如何在运行时动态查询房间数据库?
How to dynamically query the room database at runtime?
问题
是否可以在运行时构建查询?
用例
@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);
limit
部分是可选的。也就是说,它应该能够在有或没有限制的情况下执行相同的查询。
更复杂的用例
在前面的例子中,有和没有限制部分的两个静态查询是可以的,每次都可以使用适当的一个。但有时我们可能不得不处理更复杂的情况,例如构建过滤器。
在那种情况下,与前面的示例不同,将有多个可选部分。对于table本图书,我们可能需要根据图书所属类别、作者姓名、价格范围、出版日期等进行过滤,这些部分的所有组合几乎不可能进行静态查询。
根据我使用 Room 的经验(短期),这是不可能的,并不是因为 Room 的限制,而是因为@CommonsWare 隐含地评论说,这是对 SQLite 的限制。您需要两个查询,因此在您的 DAO 中需要两个方法。
我会做类似的事情:
@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);
然后在其他地方做旁路:
if (limit.isPresent()) {
return playlistDao.searchPlaylists(title, limit.get());
} else {
return playlistDao.searchPlaylists(title);
}
这是我目前能想到的最佳选择。
我没有编写多个查询,而是将负值传递给 limit 子句。因为如果查询发生变化,我必须更新更容易出错的两个查询。
官方文档 -> 如果 LIMIT 表达式的计算结果为负值,则返回的行数没有上限。 你可以在这里找到它 https://sqlite.org/lang_select.html 并阅读限制条款部分。
所以我会做这样的事情,
@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);
并在不想应用过滤器时传递负数。
return playlistDao.searchPlaylists(title, limit.isPresent() ? limit.get() : -1)
它对我有效。
更新 [2018 年 12 月 21 日]
如果您使用的是 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
使其与 Java 兼容。它为 Java 生成两个单独的方法。
@Anderson K 和@Juanky Soriano,
我同意@CommonsWare,
Room Library 有一些限制,那么我们也可以使用 Support SQLite Database
的 @query()
在 Room Database 上编写完全动态的查询
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);
现在您可以将光标行数据转换为 POJO class。
Room 中没有可选参数之类的东西,但有一个 @RawQuery 注释,您可以在其中将查询作为字符串传递,这样您就可以在运行时构建 SQL 查询。我认为这对你有用。
以下是官方文档中的示例:
@Dao
interface RawDao {
@RawQuery
User getUser(String query);
}
下面是您的使用方法:
User user = rawDao.getUser("SELECT * FROM User WHERE id = 3 LIMIT 1");
重要提示: RawQuery 方法必须 return 一个 non-void 类型
重要提示:这在 Room 1.1.0-alpha3
中可用
Room 支持 @RawQuery
注释以在 运行 时构建查询。
第 1 步:制作 DAO 方法
用 @RawQuery
注释而不是正常的 @Query
标记 DAO 方法。
@Dao
interface BooksDao{
@RawQuery
List<Book> getBooks(SupportSQLiteQuery query);
}
第 2 步:构造查询
Room 使用准备好的语句进行安全和编译时验证。因此,在构造查询时,我们需要分别存储查询字符串和绑定参数。
在此示例中,我使用变量 queryString
作为查询字符串,使用 args
作为绑定参数。
(请注意,我使用文本编辑器编写代码。因此可能存在拼写错误或简单的语法错误。如果您发现任何问题,请在评论中告诉我或编辑 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 += ";";
第 3 步:执行查询
SimpleSQLiteQuery query = new SimpleSQLiteQuery(queryString, args.toArray());
List<Book> result = booksDao.getBooks(query);
备注
- 与正常情况一样
Query
,RawQuery
支持返回原始游标、实体、POJO 和带有嵌入字段的 POJO
RawQuery
支持关系
使用 SupportSQLiteQuery。
https://developer.android.com/reference/android/arch/persistence/db/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.
@Dao
interface RawDao {
@RawQuery(observedEntities = User.class)
LiveData<List<User>> getUsers(SupportSQLiteQuery query);
}
用法:
LiveData<List<User>> liveUsers = rawDao.getUsers( new
SimpleSQLiteQuery("SELECT * FROM User ORDER BY name DESC"));
将您的 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"
注意:如果您升级到 1.1.1,并且使用 String 而不是 SupportSQLiteQuery,
你会得到错误:
RawQuery does not allow passing a string anymore. Please use
android.arch.persistence.db.SupportSQLiteQuery.
如上使用SupportSQLiteQuery即可解决问题。
注意:
确保传入 SupportSQLiteQuery 查询参数,否则会出现此错误:
RawQuery methods should have 1 and only 1 parameter with type String
or SupportSQLiteQuery
让它更简单。我将向您展示使用两个变量的 where 子句的示例。这样做:
@Query("SELECT * FROM Student WHERE stdName1= :myname AND stdId1=:myid")
List<Student> fetchAllData(String myname,int myid);
stdName1 和 stdId1 是列名
@Query("select * from task where state = :states and sentdate between :fromdate and :todate")
List<Task> getFilterAll(String states, String fromdate, String todate);
这里需要用到列名state。每当需要实现自定义查询时,只需通过 activity 中的参数传递值,否则 fragement 将进入我们将在查询中应用的界面。就像上面代码中的例子 (:fromdate
, :todate
)
冒号是必须的。您将在查询中使用哪个参数,我们将在以 :
符号开始之前提到。
不推荐使用原始查询
对于 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>>
问题
是否可以在运行时构建查询?
用例
@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);
limit
部分是可选的。也就是说,它应该能够在有或没有限制的情况下执行相同的查询。
更复杂的用例
在前面的例子中,有和没有限制部分的两个静态查询是可以的,每次都可以使用适当的一个。但有时我们可能不得不处理更复杂的情况,例如构建过滤器。
在那种情况下,与前面的示例不同,将有多个可选部分。对于table本图书,我们可能需要根据图书所属类别、作者姓名、价格范围、出版日期等进行过滤,这些部分的所有组合几乎不可能进行静态查询。
根据我使用 Room 的经验(短期),这是不可能的,并不是因为 Room 的限制,而是因为@CommonsWare 隐含地评论说,这是对 SQLite 的限制。您需要两个查询,因此在您的 DAO 中需要两个方法。
我会做类似的事情:
@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);
然后在其他地方做旁路:
if (limit.isPresent()) {
return playlistDao.searchPlaylists(title, limit.get());
} else {
return playlistDao.searchPlaylists(title);
}
这是我目前能想到的最佳选择。
我没有编写多个查询,而是将负值传递给 limit 子句。因为如果查询发生变化,我必须更新更容易出错的两个查询。
官方文档 -> 如果 LIMIT 表达式的计算结果为负值,则返回的行数没有上限。 你可以在这里找到它 https://sqlite.org/lang_select.html 并阅读限制条款部分。
所以我会做这样的事情,
@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);
并在不想应用过滤器时传递负数。
return playlistDao.searchPlaylists(title, limit.isPresent() ? limit.get() : -1)
它对我有效。
更新 [2018 年 12 月 21 日]
如果您使用的是 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
使其与 Java 兼容。它为 Java 生成两个单独的方法。
@Anderson K 和@Juanky Soriano, 我同意@CommonsWare,
Room Library 有一些限制,那么我们也可以使用 Support SQLite Database
的@query()
在 Room Database 上编写完全动态的查询
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);
现在您可以将光标行数据转换为 POJO class。
Room 中没有可选参数之类的东西,但有一个 @RawQuery 注释,您可以在其中将查询作为字符串传递,这样您就可以在运行时构建 SQL 查询。我认为这对你有用。
以下是官方文档中的示例:
@Dao
interface RawDao {
@RawQuery
User getUser(String query);
}
下面是您的使用方法:
User user = rawDao.getUser("SELECT * FROM User WHERE id = 3 LIMIT 1");
重要提示: RawQuery 方法必须 return 一个 non-void 类型
重要提示:这在 Room 1.1.0-alpha3
中可用Room 支持 @RawQuery
注释以在 运行 时构建查询。
第 1 步:制作 DAO 方法
用 @RawQuery
注释而不是正常的 @Query
标记 DAO 方法。
@Dao
interface BooksDao{
@RawQuery
List<Book> getBooks(SupportSQLiteQuery query);
}
第 2 步:构造查询
Room 使用准备好的语句进行安全和编译时验证。因此,在构造查询时,我们需要分别存储查询字符串和绑定参数。
在此示例中,我使用变量 queryString
作为查询字符串,使用 args
作为绑定参数。
(请注意,我使用文本编辑器编写代码。因此可能存在拼写错误或简单的语法错误。如果您发现任何问题,请在评论中告诉我或编辑 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 += ";";
第 3 步:执行查询
SimpleSQLiteQuery query = new SimpleSQLiteQuery(queryString, args.toArray());
List<Book> result = booksDao.getBooks(query);
备注
- 与正常情况一样
Query
,RawQuery
支持返回原始游标、实体、POJO 和带有嵌入字段的 POJO RawQuery
支持关系
使用 SupportSQLiteQuery。
https://developer.android.com/reference/android/arch/persistence/db/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.
@Dao
interface RawDao {
@RawQuery(observedEntities = User.class)
LiveData<List<User>> getUsers(SupportSQLiteQuery query);
}
用法:
LiveData<List<User>> liveUsers = rawDao.getUsers( new
SimpleSQLiteQuery("SELECT * FROM User ORDER BY name DESC"));
将您的 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"
注意:如果您升级到 1.1.1,并且使用 String 而不是 SupportSQLiteQuery,
你会得到错误:
RawQuery does not allow passing a string anymore. Please use android.arch.persistence.db.SupportSQLiteQuery.
如上使用SupportSQLiteQuery即可解决问题。
注意: 确保传入 SupportSQLiteQuery 查询参数,否则会出现此错误:
RawQuery methods should have 1 and only 1 parameter with type String or SupportSQLiteQuery
让它更简单。我将向您展示使用两个变量的 where 子句的示例。这样做:
@Query("SELECT * FROM Student WHERE stdName1= :myname AND stdId1=:myid")
List<Student> fetchAllData(String myname,int myid);
stdName1 和 stdId1 是列名
@Query("select * from task where state = :states and sentdate between :fromdate and :todate")
List<Task> getFilterAll(String states, String fromdate, String todate);
这里需要用到列名state。每当需要实现自定义查询时,只需通过 activity 中的参数传递值,否则 fragement 将进入我们将在查询中应用的界面。就像上面代码中的例子 (:fromdate
, :todate
)
冒号是必须的。您将在查询中使用哪个参数,我们将在以 :
符号开始之前提到。
对于 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>>