在 Google I/O 2019,咱们分享了 Room 2.2 的最新进展。只管过后曾经反对了很多性能,如 反对 Flow API,反对预填充数据库,反对一对一及多对多数据库关系,然而开发者们对 Room 有着更高的冀望,咱们也致力于此,在 2.2.0 – 2.4.0 版本中公布了很多开发者们期待的新性能!包含自动化迁徙,关系查询方法以及反对 Kotlin Symbol Processing (KSP) 等等。上面咱们就来逐个介绍这些新性能!
如果您更喜爱通过视频理解此内容,请在此处查看:
https://www.bilibili.com/vide…
△ 深入探讨 Room 2.4.0 的最新进展
自动化迁徙
在谈自动化迁徙之前,先看看什么是数据库迁徙。如果您更改了数据库 schema,就须要依据数据库版本进行迁徙,以防用户设施内置数据库中现有数据失落。
如果您应用 Room,那么在 数据库迁徙 过程中会进行查看并验证更新后的 schema,另外您也能够在 @Database 中设置 exportSchema,来导出 schema 信息。
对于 Room 2.4.0 版本之前的数据库迁徙,您须要实现 Migration 类,并在其中编写大量简单简短的 SQL 语句,来解决不同版本之间的迁徙。这种手动迁徙的模式,非常容易引发各种谬误。
当初 Room 反对了主动迁徙,让咱们通过两个示例来比照手动迁徙和主动迁徙:
批改表名
假如有一个蕴含两个表的数据库,表名别离是 Artist 和 Track,当初想要将表名 Track 改为 Song。
如果应用手动迁徙,必须编写和执行 SQL 语句能力更改,须要如下操作:
val MIGRATION_1_2: Migration = Migration(1, 2) {fun migrate(database: SupportSQLiteDatabase) {database.execSQL("ALTER TABLE `Track` RENAME TO `Song`")
}
}
如果应用主动迁徙,您只须要在定义数据库时增加 @AutoMigration 配置,同时提供两个版本数据库导出的 schema。Auto Migration API 将为您生成并实现 migrate 函数,编写并执行迁徙所需的 SQL 语句。代码如下:
@Database(
version = MusicDatabase.LATEST_VERSION
entities = {Song.class, Artist.class}
autoMigrations = {@AutoMigration (from = 1,to = 2)
}
exprotSchema = true
)
批改字段名
当初,演示一个更简单的场景,假如咱们要将 Artist 表中的 singerName 字段批改为 artistName。
尽管这看起来很简略,然而因为 SQLite 并没有提供用于此操作的 API,因而咱们须要依据 ALERT TABLE 实现,有如下几步操作:
- 获取须要执行更改的表
- 创立一个新表,满足更改后的表构造
- 将旧表的数据插入到新表中
- 删除旧表
- 把新表重命名为原表名称
- 进行外键查看
迁徙代码如下:
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
假如在同一迁徙中有多个更改须要配置,咱们还能够通过这些可复用的注解简化解决。
测试主动迁徙
假如您在一开始就应用了主动迁徙,当初心愿测试其是否失常工作,能够应用现有的 MigrationTestHelper API 无需任何更改。如以下代码:
@Test
fun v1ToV2() {
val helper = MigrationTestHelper(InstrumentationRegisty.getInstrumentation(),
AutoMigrationDbKotlin::class.java
)
val db: SupportSQLiteDatabase = helper.runMigrationsAndValidate(
name = TEST_DB,
version = 2,
validateDroppedTables = true
)
}
在无需额定配置的状况下,MigrationTestHelper 将主动运行并验证所有主动迁徙。在 Room 外部,如果存在主动迁徙,它们将主动增加到须要运行和验证的迁徙列表中。
须要留神的是,开发者提供的迁徙具备更高的优先级,也就是说,如果您定义主动迁徙的两个版本之间,曾经定义了手动迁徙,那么手动迁徙将优先于主动迁徙。
关系查询方法
关系查问也是新增的一个重要性能,咱们还是用一个示例阐明。
假如咱们应用与之前雷同的数据库和表,当初表名别离为 Artist 和 Song。如果咱们心愿取得音乐人到歌曲的映射汇合,就要在 artistName 和 songName 之间建设关系。如下图中 Purple Lloyd 与其热门歌曲《Another Tile in the Ceiling》和《The Great Pig in the Sky》匹配,AB/CD 将与其热门歌曲《Back in White》和《Highway to Heaven》匹配。
应用 @Relation
如果应用 @Relation 和 @Embedded 反应该映射关系,则有如下代码:
data class ArtistAndSongs(
@Embedded
val artist: Artist,
@Relation(...)
val songs: List<Song>
)
@Query("SELECT * FROM Artist")
fun getArtistsAndSongs(): List<ArtistAndSongs>
在此计划中,咱们创立了全新的 数据类,将音乐人和歌曲列表相关系。然而这种额定创立 data 类的形式,容易造成代码繁冗的问题。而 @Relation 中并不反对过滤、排序、分组或组合键,其设计初衷也是用于数据库中只有一些简略的关系,尽管受限于关系后果,但这是一种疾速实现较简略工作的便捷办法。
所以为了反对简单关系的解决,咱们并没有扩大 @Relation,而是心愿您充分发挥 SQL 的潜能,因为它的性能十分弱小。
接下来让咱们来看看 Room 如何利用全新的性能来解决这一问题。
应用全新关系查问性能
为了示意后面所示的音乐人与其歌曲之间的关系,咱们当初能够编写一个简略的 DAO 办法,其返回类型为 Map,而咱们须要做的仅仅是提供 @Query 和返回标记,Room 将为您解决其余的所有!相干代码如下:
@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 在映射的应用中更加灵便。
MapInfo 是用于阐明开发者配置的辅助程序 API,相似于后面谈到的主动迁徙更改注解。您能够应用 MapInfo 明确阐明您心愿如何解决查问到的 Cursor 所蕴含的信息。应用 MapInfo 注解您能够指定输入的数据结构中用于查问的 Key 和 Value 所映射的列。须要留神,用于 Key 的类型必须实现 equals 和 hashCode 函数因为这对映射过程十分重要。
假如咱们心愿以 artistName 作为 Key,取得歌曲列表作为 Value,则代码实现如下:
@MapInfo(keyColumn = "artistName")
@Query("SELECT * FROM Artist JOIN Song ON Artist.artistName = Song.songArtistName")
fun getArtistNameToSongs(): Map<String, List<Song>>
在该示例中,artistName 用作 Key,音乐人被映射到其歌曲名称列表,最初 artistName 被映射到其歌曲名称列表。
MapInfo 注解使您能够灵便地应用特定列,而不是整个 data 类从而进行更加自定义的映射。
其余劣势
关系查询方法的另一个益处是反对更多的数据操作,能够通过这个新性能来反对分组、筛选等性能。示例代码如下:
@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>
最初须要留神多重映射是一个外围返回类型,能够应用 Room 曾经反对的各种可察看类型封装 (包含 LiveData、Flowable、Flow)。因而,关系查询方法可让您轻松地在数据库中定义任意数量的关联关系。
更多新性能
内置 Enum 类型转换器
当初,如果零碎未提供任何类型转换器,Room 将默认应用 “ 枚举 – 字符串 ” 双向类型转换器。如果已存在实用于枚举的类型转换器,Room 将优先应用该转换器,而不应用默认转换器。
反对查问回调
当初,Room 提供了一个通用 callback API RoomDatabase.QueryCallback,此 API 会在执行查问时被调用,这将十分有助于咱们在 Debug 模式下记录日志。可通过 RoomDatabase.Builder#setQueryCallback() 设置此回调。
如果您心愿记录查问以理解数据库中产生了什么,该性能能够帮忙您进行记录,示例代码如下:
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 编译器上以原生形式运行注解处理器,从而显著缩短构建工夫。
对于 Room,应用 KSP 有如下益处:
- 进步 2 倍的构建速度;
- 间接解决 Kotlin 代码,更好的反对空平安。
随着 KSP 的稳固,Room 将应用其性能实现 value 类、生成 Kotlin 代码等。
从 KAPT 迁徙到 KSP 非常简单,只需应用 KSP 插件替换 KAPT 插件,并应用 KSP 配置 Room 注解处理器,示例代码如下:
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 更新感到兴奋,记得查看并开始在您的利用中应用这些新性能!
欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!