Jetpack Room 测试(下)——Query

587 阅读4分钟

这是我参与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查询语法中重要的有聚合和过滤俩个模块。

1637075581.png

上图用红色标注的就是聚合和过滤俩个模块,黄色标注的是对过滤的补充解释。我们一般用到的聚合函数会有:

  • 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)
}

直接在参数中拼接上通配符,也是可以达到同样效果,俩种方式应用场景不一样,看你选择。