这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
到了查询这一步,Room中才开始使用到SQL。Sqlite支持了标准SQL的绝大部分,对于你熟悉常用的SQL语法几乎都有支持,其他平台的独有功能它当然是不支持的。不过它也有些自己的拓展,例如TFS(全文搜索)拓展,这个功能在客户端中经常能用到,Room也友好地支持了这个拓展,不过这个知识点的具体展开不在此章节,敬请期待:)
@Query简介
@Query看上去像是查询注解,实际上它支持 4 种类型的语句:SELECT、INSERT、UPDATE 和 DELETE。它就像一个SQL的解析器,不过我们一般还是拿它来做查询。
不过,在实践之前你还需要在模块级的build.gradle文件中添加以下代码:
defaultConfig {
// ...
javaCompileOptions {
annotationProcessorOptions {
arguments += ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
不加的话,你可能会在执行@Query语句的时候,报错“Schema export directory is not provided to the annotation processor so we cannot export the schema. You can either provide room.schemaLocation annotation processor argument OR set exportSchema to false.”
在做查询功能测试之前,我还需要先说明一下,我已经在测试文件的@Before方法中往数据库加入了一些歌曲和歌单的信息。这里分享一个小技巧,正常我们编写的测试案例和数据都很可能被复用的,所以你最好在测试新的东西时,新建一个测试环境,也就是一个文件。在这里我们仅需要测试数据库相关的,只需要在新文件中把@Before和@After复制过来就行。当然同一个测试文件(环境)也可以供类似的测试案例使用。
相关语法
简单的SQL查询语法中重要的有聚合和过滤俩个模块。
上图用红色标注的就是聚合和过滤俩个模块,黄色标注的是对过滤的补充解释。我们一般用到的聚合函数会有:
- COUNT 行数
- MAX 最大值
- MIN 最小值
- SUM 数值列计算总和
- AVG 某列的平均值
- ABS 数值参数的绝对值
- UPPER 把字符串转换为大写字母
- LOWER 把字符串转换为小写字母
这些函数一般是对过滤后的数据使用的。
SQLite的过滤主要以 WHERE 子句为主,WHERE表示指定从一个表或多个表中获取数据的条件。你可以在WHERE后接一堆条件,或者嵌套子查询,这些在Sqlite中都是被允许的,不过在Room是不完全被允许的。 具体如何查询,只需要简单使用的话请查看菜鸟教程的sqlite,进阶版可以看sqlite官网,在这里我将介绍俩个使用@Query的使用经验。
Room中SQL的限制
查询结果的严格限制
以之前的歌曲表为例,我们可能需要通过歌手名搜到一些歌曲。那我们的测试案例和实现函数可能会这样写:
// RoomTest2.kt
@Test
fun testQuery3() {
val songList = dao.findSongs("周杰伦")
assertEquals(3, songList.size)
}
// TestDao.kt
@Query("SELECT songName FROM Song WHERE singer=:singer")
fun findSongs(singer:String): List<String>
以上的测试完全没有问题,值得注意的是findSongs 函数返回的是一个字符串列表,这很符合我们的直觉,不过你不仅需要查歌名呢,还有歌词呢,当你仅需要查部分字段的时候,那你的函数可能会像以下代码:
@Query("SELECT songName,words FROM Song WHERE singer=:singer")
fun findSongsDetail(singer:String): List<Pair<String,String>>
不过这段代码是无法编译通过的,虽然 songName和 words 字段都是 String类型,不过直接用一个二元组Pair包裹的类型是不能被Room通过的。我们需要做的是声明一个新的数据类SongPair,将songName和 words 字段作为它的属性,这样的SongPair才是可以被Room识别的。
data class SongPair(
val songName: String,
val words: String
)
我们只需要把代码像下面一样修改就可以通过测试了。
@Query("SELECT songName,words FROM Song WHERE singer=:singer")
fun findSongsDetail(singer:String): List<SongPair>
无法直接使用通配符
有时候我们需要模糊查询,我们可以使用 LIKE或者GLOB 后面加上通配符来实现效果。LIKE与GLOB的区别是LIKE是大小写不敏感的,GLOB是敏感的,另外LIKE的通配符是"_"和"%",GLOB的通配符是"*"和"?"。
不过 Room 的@Query注解中无法像SQL一样直接使用通配符的,是需要特殊写法。例如我们需要查歌曲库中名字中有”周“的歌手的歌,案例如下:
@Test
fun testQuery5() {
val songPairList = dao.easierFindSongsDetail("周")
assertEquals(3, songPairList.size)
}
那实现就应该这样写:
@Query("SELECT songName,words FROM Song WHERE singer LIKE '%' || :str || '%'")
fun easierFindSongsDetail(str:String): List<SongPair>
通过||来拼接特殊的字符。又或者你可以这样写:
// RoomTest.kt
@Query("SELECT songName,words FROM Song WHERE singer LIKE :str")
fun easierFindSongsDetail2(str:String): List<SongPair>
// TestDao.kt
@Test
fun testQuery6() {
val songPairList = dao.easierFindSongsDetail("%周%")
assertEquals(3, songPairList.size)
}
直接在参数中拼接上通配符,也是可以达到同样效果,俩种方式应用场景不一样,看你选择。