深入探讨 Room 2.4.0 的最新进展
在 Google I/O 2019,我们分享了 Room 2.2 的最新进展。尽管当时已经支持了很多功能,如支持 Flow API,支持预填充数据库,支持一对一及多对多数据库关系,但是开发者们对 Room 有着更高的期望,我们也致力于此,在 2.2.0 - 2.4.0 版本中发布了很多开发者们期待的新功能!包括自动化迁移,关系查询方法以及支持 Kotlin Symbol Processing (KSP) 等等。下面我们就来逐一介绍这些新功能!
如果您更喜欢通过视频了解此内容,请在此处查看:
Bilibili 视频链接
https://www.bilibili.com/video/BV1LR4y1M7R9/
支持预填充数据库
https://developer.android.google.cn/training/data-storage/room/prepopulateKotlin Symbol Processing (KSP)
https://github.com/google/ksp
自动化迁移
在谈自动化迁移之前,先看看什么是数据库迁移。假如您更改了数据库 schema,就需要根据数据库版本进行迁移,以防用户设备内置数据库中现有数据丢失。
如果您使用 Room,那么在数据库迁移过程中会进行检查并验证更新后的 schema,另外您也可以在 @Database 中设置 exportSchema,来导出 schema 信息。
数据库迁移
https://developer.android.google.cn/training/data-storage/room/migrating-db-versions
对于 Room 2.4.0 版本之前的数据库迁移,您需要实现 Migration 类,并在其中编写大量复杂冗长的 SQL 语句,来处理不同版本之间的迁移。这种手动迁移的形式,非常容易引发各种错误。
现在 Room 支持了自动迁移,让我们通过两个示例来对比手动迁移和自动迁移:
修改表名
val MIGRATION_1_2: Migration = Migration(1, 2) {
fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `Track` RENAME TO `Song`")
}
}
@Database(
version = MusicDatabase.LATEST_VERSION
entities = {Song.class, Artist.class}
autoMigrations = {
@AutoMigration (from = 1,to = 2)
}
exprotSchema = true
)
修改字段名
现在,演示一个更复杂的场景,假设我们要将 Artist 表中的 singerName 字段修改为 artistName。
获取需要执行更改的表
创建一个新表,满足更改后的表结构
将旧表的数据插入到新表中
删除旧表
把新表重命名为原表名称
进行外键检查
迁移代码如下:
val MIGRATION_1_2: Migration = Mirgation(1, 2) {
fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `_new_Artist`(`id` INTEGER NOT
NULL, artistName` TEXT, PRIMARY KEY(`id`)"
)
db.execSQL("INSERT INTO `_new_Artist` (id,artistName)
SELECT id, singerName FROM `Artist`"
)
db.execSQL("DROP TABLE `Artist`")
db.execSQL("ALTER TABLE `_new_Artist` RENAME TO `Artist`")
db.execSQL("PRAGMA foreign_key_check(`Artist`)")
}
}
从上面的代码就可以看出,如果使用手动迁移,即使两个版本之间仅有一处更改,也可能需要繁琐的操作,并且这些操作极易出错。
那我们来看看自动迁移该如何使用。在上面的示例中,自动迁移无法直接处理重命名表中的某一列,因为 Room 在进行自动迁移时,会遍历两个版本的数据库 schema,通过比较来检测两者之间的更改。在处理列或者表的重命名时,Room 无法明确发生了什么更改,此时可能有两种情况,是删除后新添加的?还是进行了重命名?处理列或者表的删除操作时也会有同样问题。
所以我们需要给 Room 添加一些配置来说明这些不确定的场景——定义 AutoMigrationSpec。AutoMigrationSpec 是定义自动迁移规范的接口,我们需要实现该类,并在实现类上添加和修改相对应的注解。本例中,我们使用 @RenameColumn 注解,并在注解参数中,提供表名、列的原始名称以及更新后的名称。如果在迁移完成之后,还需要执行其他任务,可以在 AutoMigrationSpec 的 onPostMigrate 函数中进行处理,相关代码如下:
@RenameColumn(
tableName = "Artist",
fromColumnName = "singerName",
toColumnName = "artistName"
)
static class MySpec : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// 迁移工作完成后处理任务的回调
}
}
完成 AutoMigrationSpec 的实现后,还需要将其添加到数据库定义时配置的 @AutoMigation 中,同时提供两个版本的数据库 schema,Auto Migration API 将生成和实现 migrate 函数,配置代码如下:
@Database(
version = MusicDatabase.LATEST_VERSION
entities = {Song.class, Artist.class}
autoMigrations = {
@AutoMigration (from = 1,to = 2,spec = MySpec.class)
}
exprotSchema = true
)
上面的案例提到了 @RenameColumn,相关的变更处理注解有如下几种:
@DeleteColumn @DeleteTable
@RenameColumn
@RenameTable
假设在同一迁移中有多个更改需要配置,我们还可以通过这些可复用的注解简化处理。
测试自动迁移
@Test
fun v1ToV2() {
val helper = MigrationTestHelper(
InstrumentationRegisty.getInstrumentation(),
AutoMigrationDbKotlin::class.java
)
val db: SupportSQLiteDatabase = helper.runMigrationsAndValidate(
name = TEST_DB,
version = 2,
validateDroppedTables = true
)
}
关系查询方法
使用 @Relation
data class ArtistAndSongs(
@Embedded
val artist: Artist,
@Relation(...)
val songs: List<Song>
)
@Query("SELECT * FROM Artist")
fun getArtistsAndSongs(): List<ArtistAndSongs>
数据类
https://www.kotlincn.net/docs/reference/data-classes.html
使用全新关系查询功能
@Query("SELECT * FROM Artist JOIN Song ON Artist.artistName = Song.songArtistName")
fun getAllArtistAndTheirSongsList(): Map<Artist, List<Song>>
在 Room 内部,实际上要做的是找到音乐人、歌曲和 Cursor 并将它们放入 Map 中的 Key 和 Value 中。
// 一对一映射关系
@Query("SELECT * FROM Song JOIN Artist ON Song.songArtistName = Artist.artistName")
fun getSongAndArtist(): Map<Song, Artist>
使用 @MapInfo
@MapInfo(keyColumn = "artistName")
@Query("SELECT * FROM Artist JOIN Song ON Artist.artistName = Song.songArtistName")
fun getArtistNameToSongs(): Map<String, List<Song>>
其他优势
@MapInfo(valueColumn = "songCount")
@Query("
SELECT *, COUNT(songId) as songCount FROM Artist JOIN Song ON
Artist.artistName = Song.songArtistName
GROUP BY artistName WHERE songCount = 2
")
fun getArtistAndSongCountMap(): Map<Artist, Integer>
更多新功能
内置 Enum 类型转换器
支持查询回调
fun setUp() {
database = databaseBuilder.setQueryCallback(
RoomDatabase.QueryCallback{ sqlQuery, bindArgs ->
// 记录所有触发的查询
Log.d(TAG, "SQL Query $sqlQuery")
},
myBackgroundExecutor
).build()
}
支持原生 Paging 3.0 API
Room 现在支持为返回值类型为 androidx.paging.PagingSource 且带 @Query 注解的方法生成实现。
支持 RxJava3
Room 现在支持 RxJava3 类型。通过依赖 androidx.room:room-rxjava3,您可以声明返回值类型为 Flowable、Single、Maybe 和 Completable 的 DAO 方法。
支持 Kotlin Symbol Processing (KSP)
KSP 用于替代 KAPT,它能够在 Kotlin 编译器上以原生方式运行注解处理器,从而显著缩短构建时间。
提高 2 倍的构建速度; 直接处理 Kotlin 代码,更好的支持空安全。
plugins{
// 使用 KSP 插件替换 KATP 插件
// id("kotlin-kapt")
id("com.google.devtools.ksp")
}
dependencies{
// 使用 KSP 配置替代 KAPT
// kapt "androidx.room:room-compiler:$version"
ksp "androidx.room:room-compiler:$version"
}
总结
自动化迁移、关系查询方法、KSP——Room 带来了很多新功能,希望大家和我们一样对所有这些 Room 更新感到兴奋,记得查看并开始在您的应用中使用这些新功能!
推荐阅读