简体   繁体   English

如何在运行时动态查询房间数据库?

[英]How to dynamically query the room database at runtime?

The problem问题

Is it possible construct a query at runtime?是否可以在运行时构造查询?


Use case用例

@Query("SELECT * FROM playlist " +
        "WHERE playlist_title LIKE '% :playlistTitle %' " +
        "GROUP BY playlist_title " +
        "ORDER BY playlist_title " +
        "LIMIT :limit")
 List<IPlaylist> searchPlaylists(String playlistTitle, int limit);

The limit part is optional. limit部分是可选的。 That is, it should be able to perform the same query with or without limit.也就是说,它应该能够在有或没有限制的情况下执行相同的查询。


A more complicated use case更复杂的用例

In the previous case, it is possible to make two static queries with and without limit part and appropriate one can be used each time.在前一种情况下,可以做两个有限制和没有限制部分的静态查询,每次都可以使用适当的查询。 But sometimes we may have to deal with more complex situations like building a filter.但有时我们可能需要处理更复杂的情况,比如构建过滤器。

In that case, unlike the previous example, there will be multiple number of optional parts.在这种情况下,与前面的示例不同,将有多个可选部件。 For a table of books, we may need to do filtering according to the category the book belongs to, author name, price range, publication date etc. It is almost impossible to make static queries with all combinations of these parts.对于图书表,我们可能需要根据图书所属的类别、作者姓名、价格区间、出版日期等进行过滤。几乎不可能对这些部分的所有组合进行静态查询。

Room supports @RawQuery annotation to construct queries at run-time. Room 支持@RawQuery注释以在运行时构造查询。


Step 1 : Make DAO method第 1 步:制作 DAO 方法

Mark the DAO method with @RawQuery annotation instead of normal @Query .使用@RawQuery注释而不是普通的@Query标记 DAO 方法。

@Dao
interface BooksDao{
    @RawQuery
    List<Book> getBooks(SupportSQLiteQuery query);
}

Step 2 : Construct the query第 2 步:构建查询

Room uses prepared statements for security and compile time verification. Room 使用准备好的语句进行安全性和编译时验证。 Therefore, while constructing queries, we need to store query string and bind parameters separately.因此,在构造查询时,我们需要分别存储查询字符串和绑定参数。

In this example, I use the variable queryString for query string and args for bind parameters.在这个例子中,我使用变量queryString作为查询字符串和args作为绑定参数。

(Please note that I used text editor to write code. Therefore there may be typo or simple syntax errors. If you find anything please let me know in the comments or edit the post.) (请注意,我使用文本编辑器编写代码。因此可能存在拼写错误或简单的语法错误。如果您发现任何问题,请在评论中告诉我或编辑帖子。)

// Query string
String queryString = new String();

// List of bind parameters
List<Object> args = new ArrayList();

boolean containsCondition = false;

// Beginning of query string
queryString += "SELECT * FROM BOOKS";

// Optional parts are added to query string and to args upon here

if(!authorName.isEmpty()){
    queryString += " WHERE";
    queryString += " author_name LIKE ?%";
    args.add(authorName);
    containsCondition = true;
}

if(fromDate!=null){
    
    if (containsCondition) {
        queryString += " AND";
    } else {
        queryString += " WHERE";
        containsCondition = true;
    }

    queryString += " publication_date AFTER ?";
    args.add(fromDate.getTime());
}

if(toDate!=null){
    
    if (containsCondition) {
        queryString += " AND";
    } else {
        queryString += " WHERE";
        containsCondition = true;
    }

    queryString += " publication_date BEFORE ?";
    args.add(toDate.getTime());
}

// End of query string
queryString += ";";

Step 3 : Perform query第 3 步:执行查询

SimpleSQLiteQuery query = new SimpleSQLiteQuery(queryString, args.toArray());
List<Book> result = booksDao.getBooks(query);



Notes笔记

  • Like normal Query , RawQuery supports returning raw cursors, entities, POJOs and POJOs with embedded fields与普通Query一样, RawQuery支持返回原始游标、实体、POJO 和带有嵌入字段的 POJO
  • RawQuery supports relations RawQuery支持关系

In my experience (short) using Room that's not possible, and not because of being a Room limitation but, as implicitly commented by @CommonsWare , a limitation on SQLite.根据我的经验(简短),使用 Room 是不可能的,并不是因为 Room 限制,而是正如 @CommonsWare 隐式评论的那样,是对 SQLite 的限制。 You need two queries, and therefore two methods in your DAO.您需要两个查询,因此您的 DAO 中有两种方法。

I would do have something like:我会做类似的事情:

@Query("SELECT * FROM playlist " +
    "WHERE playlist_title LIKE '% :playlistTitle %' " +
    "GROUP BY playlist_title " +
    "ORDER BY playlist_title " +
    "LIMIT :limit")
List<IPlaylist> searchPlaylists(String playlistTitle, int limit);

@Query("SELECT * FROM playlist " +
    "WHERE playlist_title LIKE '% :playlistTitle %' " +
    "GROUP BY playlist_title " +
    "ORDER BY playlist_title ")
List<IPlaylist> searchPlaylists(String playlistTitle);

Then somewhere else you do the bypass:然后在其他地方你做旁路:

if (limit.isPresent()) {
   return playlistDao.searchPlaylists(title, limit.get());
} else {
   return playlistDao.searchPlaylists(title);
}

That 's the best option I can think at the moment.这是我目前能想到的最佳选择。

Instead of writing multiple query i refer pass negative value to limit clause.我没有编写多个查询,而是将负值传递给限制子句。 Because if there is change in query i have to update the both query which is more error prone.因为如果查询发生变化,我必须更新更容易出错的两个查询。

Official doc -> If the LIMIT expression evaluates to a negative value, then there is no upper bound on the number of rows returned.官方文档 ->如果 LIMIT 表达式的计算结果为负值,则返回的行数没有上限。 you can find it here https://sqlite.org/lang_select.html and read the limit clause section.你可以在这里找到它https://sqlite.org/lang_select.html并阅读限制条款部分。

So I would do somthing like this,所以我会做这样的事情,

@Query("SELECT * FROM playlist " +
    "WHERE playlist_title LIKE '% :playlistTitle %' " +
    "GROUP BY playlist_title " +
    "ORDER BY playlist_title " +
    "LIMIT :limit")
List<IPlaylist> searchPlaylists(String playlistTitle, int limit);

and pass negative when you don't want to apply filter.当您不想应用过滤器时传递否定。

return playlistDao.searchPlaylists(title, limit.isPresent() ? limit.get() : -1)

It's working in my case.它在我的情况下工作。

Updated [21 Dec 2018]更新 [2018 年 12 月 21 日]

In case If you are using kotlin use default value.如果您使用的是 kotlin,请使用默认值。

@JvmOverloads
@Query("SELECT * FROM playlist " +
        "WHERE playlist_title LIKE '% :playlistTitle %' " +
        "GROUP BY playlist_title " +
        "ORDER BY playlist_title " +
        "LIMIT :limit")
fun searchPlaylists(playlistTitle: String, limit: Int = -1): List<IPlaylist>

@JvmOverloads to make it compatiable with Java. @JvmOverloads使其与 Java 兼容。 It generate two separate methods for Java.它为 Java 生成两个单独的方法。

There is no something like optional parameter in Room, but there is a @RawQuery annotation where you can pass query as a String so you can build your SQL query in the runtime. Room 中没有类似可选参数的东西,但有一个 @RawQuery 注释,您可以在其中将查询作为字符串传递,以便您可以在运行时构建 SQL 查询。 I think this will work for you.我认为这对你有用。

Here is the example from the Offical documentation:以下是官方文档中的示例:

@Dao
 interface RawDao {
     @RawQuery
     User getUser(String query);
 }

And here is how you can use it:以下是如何使用它:

User user = rawDao.getUser("SELECT * FROM User WHERE id = 3 LIMIT 1");

Important: RawQuery methods must return a non-void type重要提示: RawQuery 方法必须返回非空类型

Important: This is available in Room 1.1.0-alpha3重要提示:这在 Room 1.1.0-alpha3 中可用

Use SupportSQLiteQuery.使用 SupportSQLiteQuery。

https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteQuery https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteQuery

Latest release 1.1.1 now uses SupportSQLiteQuery.最新版本 1.1.1 现在使用 SupportSQLiteQuery。

A query with typed bindings.带有类型绑定的查询。 It is better to use this API instead of rawQuery(String, String[]) because it allows binding type safe parameters.最好使用此 API 而不是 rawQuery(String, String[]) 因为它允许绑定类型安全参数。

@Dao
     interface RawDao {
         @RawQuery(observedEntities = User.class)
         LiveData<List<User>> getUsers(SupportSQLiteQuery query);
     }

Usage:用法:

     LiveData<List<User>> liveUsers = rawDao.getUsers( new 
SimpleSQLiteQuery("SELECT * FROM User ORDER BY name DESC"));

Update your gradle to 1.1.1将您的 gradle 更新到 1.1.1

implementation 'android.arch.persistence.room:runtime:1.1.1'
implementation 'android.arch.lifecycle:extensions:1.1.1'
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"

Note: if you upgrade to 1.1.1, and are using String instead of SupportSQLiteQuery,注意:如果您升级到 1.1.1,并且使用 String 而不是 SupportSQLiteQuery,

you will get the error:你会得到错误:

RawQuery does not allow passing a string anymore. RawQuery 不再允许传递字符串。 Please use android.arch.persistence.db.SupportSQLiteQuery.请使用 android.arch.persistence.db.SupportSQLiteQuery。

Using SupportSQLiteQuery as above will solve the problem.使用上面的 SupportSQLiteQuery 可以解决这个问题。

Note: Make sure you pass in SupportSQLiteQuery query parameter or you will get this error:注意:确保传入 SupportSQLiteQuery 查询参数,否则会出现此错误:

RawQuery methods should have 1 and only 1 parameter with type String or SupportSQLiteQuery RawQuery 方法应该有 1 个且只有 1 个类型为 String 或 SupportSQLiteQuery 的参数

Make it more simple.让它更简单。 I'll show you example using where clause using two variable.我将向您展示使用 where 子句使用两个变量的示例。 Do it like this:像这样做:

  @Query("SELECT * FROM Student WHERE stdName1= :myname AND stdId1=:myid")
List<Student> fetchAllData(String myname,int myid);

stdName1 and stdId1 are column names stdName1 和 stdId1 是列名

@Anderson K & @Juanky Soriano, I'm agree with @CommonsWare, @Anderson K & @Juanky Soriano,我同意@CommonsWare,

There some Limitation in Room Library, then also We can write fully dynamic query on Room Database by using the @query() of Support SQLite Database Room 库有一些限制,那么我们也可以使用 Support SQLite Database 的@query()编写对 Room 数据库的完全动态查询

String mQuery = "SELECT * FROM foobar WHERE columnName1 IN ('value_1','value_2') and columnName2 In('value_3','value_4')";

AppDatabase appDatabase = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

Cursor mCursor = AppDatabase.getAppDatabase(context).getOpenHelper().getReadableDatabase().query(myQuery);

Now you can convert cursor row wise data to your POJO class.现在您可以将游标行数据转换为您的 POJO 类。

  @Query("select * from task where state = :states and sentdate between :fromdate and :todate")
  List<Task> getFilterAll(String states, String fromdate, String todate);

Here we need to use column name state.这里我们需要使用列名状态。 Whenever need to achieve custom query just pass the value through the parameter from the activity or fragement will get in to the interface we will apply inside the query.每当需要实现自定义查询时,只需通过来自活动的参数传递值,或者片段将进入我们将在查询中应用的界面。 like example in the above code ( :fromdate , :todate )就像上面代码中的例子( :fromdate , :todate

Colon is must.冒号是必须的。 Which parameter you will going to use inside the query we will mention before start with : symbol.您将在查询中使用哪个参数,我们将在以:符号开头之前提到。

For Kotlin-Room-ViewModel对于 Kotlin-Room-ViewModel

@Query("SELECT * FROM schedule_info_table where schedule_month = :monthValue ORDER BY schedule_date_time ASC")
fun getThisMonthSchedules(monthValue: Int): Flow<List<SchedulesInformation>>

根据文档不推荐使用原始查询

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM