在 Google I/O 2019,我們分享了 Room 2.2 的最新進(jìn)展。盡管當(dāng)時(shí)已經(jīng)支持了很多功能,如支持 Flow API,支持預(yù)填充數(shù)據(jù)庫,支持一對一及多對多數(shù)據(jù)庫關(guān)系,但是開發(fā)者們對 Room 有著更高的期望,我們也致力于此,在 2.2.0 - 2.4.0 版本中發(fā)布了很多開發(fā)者們期待的新功能!包括自動(dòng)化遷移,關(guān)系查詢方法以及支持 Kotlin Symbol Processing (KSP) 等等。下面我們就來逐一介紹這些新功能!
自動(dòng)化遷移
在談自動(dòng)化遷移之前,先看看什么是數(shù)據(jù)庫遷移。假如您更改了數(shù)據(jù)庫 schema,就需要根據(jù)數(shù)據(jù)庫版本進(jìn)行遷移,以防用戶設(shè)備內(nèi)置數(shù)據(jù)庫中現(xiàn)有數(shù)據(jù)丟失。如果您使用 Room,那么在數(shù)據(jù)庫遷移過程中會進(jìn)行檢查并驗(yàn)證更新后的 schema,另外您也可以在 @Database 中設(shè)置 exportSchema,來導(dǎo)出 schema 信息。
對于 Room 2.4.0 版本之前的數(shù)據(jù)庫遷移,您需要實(shí)現(xiàn) Migration 類,并在其中編寫大量復(fù)雜冗長的 SQL 語句,來處理不同版本之間的遷移。這種手動(dòng)遷移的形式,非常容易引發(fā)各種錯(cuò)誤。
現(xiàn)在 Room 支持了自動(dòng)遷移,讓我們通過兩個(gè)示例來對比手動(dòng)遷移和自動(dòng)遷移:
修改表名
假設(shè)有一個(gè)包含兩個(gè)表的數(shù)據(jù)庫,表名分別是 Artist 和 Track,現(xiàn)在想要將表名 Track 改為 Song。
如果使用手動(dòng)遷移,必須編寫和執(zhí)行 SQL 語句才能更改,需要如下操作:
如果使用自動(dòng)遷移,您只需要在定義數(shù)據(jù)庫時(shí)添加 @AutoMigration 配置,同時(shí)提供兩個(gè)版本數(shù)據(jù)庫導(dǎo)出的 schema。Auto Migration API 將為您生成并實(shí)現(xiàn) migrate 函數(shù),編寫并執(zhí)行遷移所需的 SQL 語句。代碼如下: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
)
修改字段名
現(xiàn)在,演示一個(gè)更復(fù)雜的場景,假設(shè)我們要將 Artist 表中的 singerName 字段修改為 artistName。
雖然這看起來很簡單,但是由于 SQLite 并沒有提供用于此操作的 API,因此我們需要根據(jù) ALERT TABLE 實(shí)現(xiàn),有如下幾步操作:
1.獲取需要執(zhí)行更改的表
2.創(chuàng)建一個(gè)新表,滿足更改后的表結(jié)構(gòu)
3.將舊表的數(shù)據(jù)插入到新表中
4.刪除舊表
5.把新表重命名為原表名稱
6.進(jìn)行外鍵檢查
遷移代碼如下:
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`)")
}
}
從上面的代碼就可以看出,如果使用手動(dòng)遷移,即使兩個(gè)版本之間僅有一處更改,也可能需要繁瑣的操作,并且這些操作極易出錯(cuò)。
那我們來看看自動(dòng)遷移該如何使用。在上面的示例中,自動(dòng)遷移無法直接處理重命名表中的某一列,因?yàn)?Room 在進(jìn)行自動(dòng)遷移時(shí),會遍歷兩個(gè)版本的數(shù)據(jù)庫 schema,通過比較來檢測兩者之間的更改。在處理列或者表的重命名時(shí),Room 無法明確發(fā)生了什么更改,此時(shí)可能有兩種情況,是刪除后新添加的?還是進(jìn)行了重命名?處理列或者表的刪除操作時(shí)也會有同樣問題。
所以我們需要給 Room 添加一些配置來說明這些不確定的場景——定義 AutoMigrationSpec。AutoMigrationSpec 是定義自動(dòng)遷移規(guī)范的接口,我們需要實(shí)現(xiàn)該類,并在實(shí)現(xiàn)類上添加和修改相對應(yīng)的注解。本例中,我們使用 @RenameColumn 注解,并在注解參數(shù)中,提供表名、列的原始名稱以及更新后的名稱。如果在遷移完成之后,還需要執(zhí)行其他任務(wù),可以在 AutoMigrationSpec 的 onPostMigrate 函數(shù)中進(jìn)行處理,相關(guān)代碼如下:
tableName = ,
fromColumnName = ,
toColumnName =
)
static class MySpec : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// 遷移工作完成后處理任務(wù)的回調(diào)
}
}
完成 AutoMigrationSpec 的實(shí)現(xiàn)后,還需要將其添加到數(shù)據(jù)庫定義時(shí)配置的 @AutoMigation 中,同時(shí)提供兩個(gè)版本的數(shù)據(jù)庫 schema,Auto Migration API 將生成和實(shí)現(xiàn) migrate 函數(shù),配置代碼如下:
@Database(
version = MusicDatabase.LATEST_VERSION
entities = {Song.class, Artist.class}
autoMigrations = {
@AutoMigration (from = 1,to = 2,spec = MySpec.class)
}
exprotSchema = true
)
上面的案例提到了 @RenameColumn,相關(guān)的變更處理注解有如下幾種:
- @DeleteColumn
- @DeleteTable
- @RenameColumn
- @RenameTable
假設(shè)在同一遷移中有多個(gè)更改需要配置,我們還可以通過這些可復(fù)用的注解簡化處理。
測試自動(dòng)遷移
假設(shè)您在一開始就使用了自動(dòng)遷移,現(xiàn)在希望測試其是否正常工作,可以使用現(xiàn)有的 MigrationTestHelper API 無需任何更改。如以下代碼:
fun v1ToV2() {
val helper = MigrationTestHelper(
InstrumentationRegisty.getInstrumentation(),
AutoMigrationDbKotlin::class.java
)
val db: SupportSQLiteDatabase = helper.runMigrationsAndValidate(
name = TEST_DB,
version = 2,
validateDroppedTables = true
)
}
在無需額外配置的情況下,MigrationTestHelper 將自動(dòng)運(yùn)行并驗(yàn)證所有自動(dòng)遷移。在 Room 內(nèi)部,如果存在自動(dòng)遷移,它們將自動(dòng)添加到需要運(yùn)行和驗(yàn)證的遷移列表中。
需要注意的是,開發(fā)者提供的遷移具有更高的優(yōu)先級,也就是說,如果您定義自動(dòng)遷移的兩個(gè)版本之間,已經(jīng)定義了手動(dòng)遷移,那么手動(dòng)遷移將優(yōu)先于自動(dòng)遷移。
關(guān)系查詢方法
關(guān)系查詢也是新增的一個(gè)重要功能,我們還是用一個(gè)示例說明。
假設(shè)我們使用與之前相同的數(shù)據(jù)庫和表,現(xiàn)在表名分別為 Artist 和 Song。如果我們希望獲得音樂人到歌曲的映射集合,就要在 artistName 和 songName 之間建立關(guān)系。如下圖中 Purple Lloyd 與其熱門歌曲《Another Tile in the Ceiling》和《The Great Pig in the Sky》匹配,AB/CD 將與其熱門歌曲《Back in White》和《Highway to Heaven》匹配。
使用 @Relation如果使用 @Relation 和 @Embedded 反應(yīng)該映射關(guān)系,則有如下代碼:
data class ArtistAndSongs(
val artist: Artist,
val songs: List
)
fungetArtistsAndSongs():List
在此方案中,我們創(chuàng)建了全新的數(shù)據(jù)類,將音樂人和歌曲列表相關(guān)系。但是這種額外創(chuàng)建 data 類的方式,容易造成代碼繁冗的問題。而 @Relation 中并不支持過濾、排序、分組或組合鍵,其設(shè)計(jì)初衷也是用于數(shù)據(jù)庫中只有一些簡單的關(guān)系,雖然受限于關(guān)系結(jié)果,但這是一種快速完成較簡單任務(wù)的便捷方法。
所以為了支持復(fù)雜關(guān)系的處理,我們并沒有擴(kuò)展 @Relation,而是希望您充分發(fā)揮 SQL 的潛能,因?yàn)樗墓δ芊浅?qiáng)大。
接下來讓我們來看看 Room 如何利用全新的功能來解決這一問題。
使用全新關(guān)系查詢功能
為了表示前面所示的音樂人與其歌曲之間的關(guān)系,我們現(xiàn)在可以編寫一個(gè)簡單的 DAO 方法,其返回類型為 Map,而我們需要做的僅僅是提供 @Query 和返回標(biāo)記,Room 將為您處理其余的一切!相關(guān)代碼如下:
fungetAllArtistAndTheirSongsList():Map>,>
在 Room 內(nèi)部,實(shí)際上要做的是找到音樂人、歌曲和 Cursor 并將它們放入 Map 中的 Key 和 Value 中。
在本例中,涉及到一對多的映射關(guān)系,其中單個(gè)音樂人映射到一個(gè)歌曲集合。當(dāng)然我們也可以使用一對一映射,如下文所示:
// 一對一映射關(guān)系
fungetSongAndArtist():Map,>
使用 @MapInfo
實(shí)際上,您可以通過 @MapInfo 在映射的使用中更加靈活。
MapInfo 是用于說明開發(fā)者配置的輔助程序 API,類似于前面談到的自動(dòng)遷移更改注解。您可以使用 MapInfo 明確說明您希望如何處理查詢到的 Cursor 所包含的信息。使用 MapInfo 注解您可以指定輸出的數(shù)據(jù)結(jié)構(gòu)中用于查詢的 Key 和 Value 所映射的列。需要注意,用于 Key 的類型必須實(shí)現(xiàn) equals 和 hashCode 函數(shù)因?yàn)檫@對映射過程非常重要。
假設(shè)我們希望以 artistName 作為 Key,獲得歌曲列表作為 Value,則代碼實(shí)現(xiàn)如下:
在該示例中,artistName 用作 Key,音樂人被映射到其歌曲名稱列表,最后 artistName 被映射到其歌曲名稱列表。fungetArtistNameToSongs():Map>,>,>,>
MapInfo 注解使您可以靈活地使用特定列,而不是整個(gè) data 類從而進(jìn)行更加自定義的映射。
其他優(yōu)勢
關(guān)系查詢方法的另一個(gè)好處是支持更多的數(shù)據(jù)操作,可以通過這個(gè)新功能來支持分組、篩選等功能。示例代碼如下:@MapInfo(valueColumn = "songCount")
@Query("
SELECT *, COUNT(songId) as songCount FROM Artist JOIN Song ON
Artist.artistName = Song.songArtistName
GROUP BY artistName WHERE songCount = 2
")
fungetArtistAndSongCountMap():Map,>
最后需要注意多重映射是一個(gè)核心返回類型,可以使用 Room 已經(jīng)支持的各種可觀察類型封裝 (包括 LiveData、Flowable、Flow)。因此,關(guān)系查詢方法可讓您輕松地在數(shù)據(jù)庫中定義任意數(shù)量的關(guān)聯(lián)關(guān)系。
更多新功能
內(nèi)置 Enum 類型轉(zhuǎn)換器
現(xiàn)在,如果系統(tǒng)未提供任何類型轉(zhuǎn)換器,Room 將默認(rèn)使用 "枚舉 - 字符串" 雙向類型轉(zhuǎn)換器。如果已存在適用于枚舉的類型轉(zhuǎn)換器,Room 將優(yōu)先使用該轉(zhuǎn)換器,而不使用默認(rèn)轉(zhuǎn)換器。
支持查詢回調(diào)現(xiàn)在,Room 提供了一個(gè)通用 callback API RoomDatabase.QueryCallback,此 API 會在執(zhí)行查詢時(shí)被調(diào)用,這將非常有助于我們在 Debug 模式下記錄日志??赏ㄟ^ RoomDatabase.Builder#setQueryCallback() 設(shè)置此回調(diào)。
如果您希望記錄查詢以了解數(shù)據(jù)庫中發(fā)生了什么,該功能可以幫助您進(jìn)行記錄,示例代碼如下:fun setUp() {
database = databaseBuilder.setQueryCallback(
RoomDatabase.QueryCallback{ sqlQuery, bindArgs ->
// 記錄所有觸發(fā)的查詢
Log.d(TAG, "SQL Query $sqlQuery")
},
myBackgroundExecutor
).build()
}
支持原生 Paging 3.0 API
Room 現(xiàn)在支持為返回值類型為 androidx.paging.PagingSource 且?guī)?@Query 注解的方法生成實(shí)現(xiàn)。
支持 RxJava3
Room 現(xiàn)在支持 RxJava3 類型。通過依賴 androidx.room:room-rxjava3,您可以聲明返回值類型為 Flowable、Single、Maybe 和 Completable 的 DAO 方法。
支持 Kotlin Symbol Processing (KSP)
KSP 用于替代 KAPT,它能夠在 Kotlin 編譯器上以原生方式運(yùn)行注解處理器,從而顯著縮短構(gòu)建時(shí)間。
對于 Room,使用 KSP 有如下好處:
- 提高 2 倍的構(gòu)建速度;
- 直接處理 Kotlin 代碼,更好的支持空安全。
隨著 KSP 的穩(wěn)定,Room 將使用其功能實(shí)現(xiàn) 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$version"
ksp "androidx.room$version"
}
總結(jié)
自動(dòng)化遷移、關(guān)系查詢方法、KSP——Room 帶來了很多新功能,希望大家和我們一樣對所有這些 Room 更新感到興奮,記得查看并開始在您的應(yīng)用中使用這些新功能!
原文標(biāo)題:深入探討 Room 2.4.0 的最新進(jìn)展
文章出處:【微信公眾號:谷歌開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
審核編輯:湯梓紅
-
Google
+關(guān)注
關(guān)注
5文章
1754瀏覽量
57380 -
設(shè)備
+關(guān)注
關(guān)注
2文章
4453瀏覽量
70494 -
自動(dòng)化
+關(guān)注
關(guān)注
29文章
5483瀏覽量
79008
原文標(biāo)題:深入探討 Room 2.4.0 的最新進(jìn)展
文章出處:【微信號:Google_Developers,微信公眾號:谷歌開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論