Android Room:Order By不起作用

48

我正在使用新的Android ORM Room。

我遇到了以下问题,带有参数的ORDER BY查询不起作用。

如果我想要使用从参数填充的字段进行ORDER BY,它就无法正常工作。它只是不会对任何东西进行排序。

@Query("SELECT * FROM User ORDER BY :orderBY ASC")
List<User> sortedFind(String orderBY);

但是,当我直接在查询中放置ORDER BY列以对结果进行排序时,它按预期工作。

@Query("SELECT * FROM User ORDER BY name ASC")
List<User> sortedFind();

这是 Android Room 的一个 bug 吗,还是我做错了什么?


1
已向谷歌报告 https://issuetracker.google.com/issues/62169706 - Sirelon
2
我猜想他们没有预料到查询语句中的那部分会是可变的。据我所知,如果你直接使用 SQLite 而不是 Room,它也不支持 SELECT * FROM User ORDER BY ? ASC 这样的语句。 - CommonsWare
@CommonsWare 是的,很有道理。谢谢回复。 - Sirelon
为了避免编写多个查询,这将是Room中一个很棒的功能。 - live-love
5个回答

14

正在发生什么

@Dao 方法的参数只能是值,而不能是查询字符串。我认为这样做的原因是为了防止 SQL 注入问题。

为什么会这样

例如,查询 SELECT * FROM emails WHERE uid = ? ,然后将值设置为 "1 OR WHERE isAdmin = true"。这将允许用户在您的数据库上运行自己定制的查询并执行所需的操作。

我的解决方案

我也遇到了这个问题。以下是我的解决方案链接。

我的解决方案有两部分。首先是DynamicQueryservice,它根据输入生成查询字符串和值数组,然后运行原始查询并返回Cursor。第二个是Cursor2POJO 映射器,因此我无需为类编写光标映射,也不会引入潜在的维护问题。我只需向我的类添加注释(与房间列名称匹配),该库就会处理剩下的部分。

我将我的游标映射器分离到自己的中,以便您使用它(我刚刚创建了这个库,如果自述文件不清晰或存在错误,请在评论中联系我)。

P.S. 我的库无法使用 Room @ColumnInfo 获取列名,因为注释当前设置为 RetentionPolicy.CLASS ,因此无法通过反射访问。(已添加到 Google 问题跟踪器https://issuetracker.google.com/issues/63720940


你不能传递查询字符串的原因更可能是我们将失去 Room 的编译时安全性,这是 Room 的一个基本优势。 - gjsalot
1
出于安全考虑,不允许使用参数进行ORDER BY操作听起来完全无理。 - Johann

14

如果您尝试对字符串字段进行排序,则可能会遇到字符串大小写问题,此时您可以尝试使用 LOWER(your_string_field_name)UPPER(your_string_field_name) ,如下所示:

@Query("SELECT * FROM User ORDER BY LOWER(name) ASC"

享受编码 :)


12

您应该使用@RawQuery注释和SupportSQLiteQuery类进行运行时查询。

在Dao中:

 @RawQuery
 List<Objects> runtimeQuery(SupportSQLiteQuery sortQuery);

获取数据:

String query ="SELECT * FROM User ORDER BY " + targetField + " ASC";
List<Objects> users = appDatabase.daoUser().runtimeQuery(new SimpleSQLiteQuery(query));

2

问题

这不是传递 ORDER BY 列的正确方式,因为 Room 数据库无法识别它。

如果将任何 String 数据传递给 @Query 语句,RoomDB 将会为其添加单引号,所以例如在您的代码中:

@Query("SELECT * FROM User ORDER BY :orderBY ASC")
List<User> sortedFind(String orderBY);

当您调用sortedFind函数时:
sortedFind("UserId");

查询RoomDB的语句应该像这样:SELECT * FROM User ORDER BY 'UserId' ASC,问题在于'UserId'不是一个列名。

正确的方法

使用SQLite CASE检查列名并根据该列设置ORDER BY列,如下所示:

@Query("SELECT * FROM User "+
        " ORDER BY  "+
        "      CASE :orderBY WHEN 'UserId' THEN UserId END DESC," +
        "      CASE :orderBY WHEN 'UserName' THEN UserName END DESC," +
        "      CASE :orderBY WHEN 'UserAge' THEN UserAge END DESC" )
List<User> sortedFind(String orderBY);

是的,这个方法有效,但是你必须小心将变量用引号括起来,而不是直接使用实际的变量。这个问题让我有点困惑了一会儿。 - undefined

1

你只能按照任何列的名称将数据按升序排列。 在编程方面,您无法向Dao方法传递任何值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接