系列文章目錄
相關文章:
Jetpack:Room超詳細使用踩坑指南!
Jetpack:Room+kotlin協程? 事務問題分析,withTransaction API 詳解.
Jetpack:Room使用報錯FAQ
Jetpack:Room配合LiveData/Flow使用優化,Room+Flow使用原理決議,
文章目錄
- 系列文章目錄
- Room升級簡介
- 例外處理
- 實戰
- 擴展知識
Room升級簡介
隨著業務的變化,資料庫可能也需要做一些調整,列如新增或則修改一個欄位等等,這時候就需要對資料庫進行升級的操作了,Android提供了一個Migration類,來對Room資料庫進行升級,
public Migration(int startVersion, int endVersion) {
this.startVersion = startVersion;
this.endVersion = endVersion;
}
Migration有兩個引數,startVersion和endVersion,startVersion表示當前資料庫版本(設備上安裝的版本),endVersion表示將要升級到的版本,如果設備中的應用程式資料庫版本為1,以下Migration會將你的資料庫版本從1升級到2,
private val MIGRATION_1_2 = object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
//執行與資料庫升級相關的操作
}
}
以此類推,如果需要將資料庫由2升級到3,則進行如下的宣告,
private val MIGRATION_2_3 = object :Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
//執行與資料庫升級相關的操作
}
}
如果當前用戶的資料庫時1,直接安裝了升級到3版本的應用,那么此時Room會按照順序先后執行Migration(1,2)、Migration(2,3)以完成升級,
最后,通過addMigration將升級方案加入到room中,
Room.databaseBuilder(
AppUtil.application,
StudentDataBase::class.java,
STUDENT_DB_NAME
)
.addMigrations(MIGRATION_1_2,MIGRATION_2_3)
.build()
例外處理
如果我們將資料庫升級到了3,但是缺沒有寫對應的migration,那么使用的時候room直接回拋出IllagelStateException,因為Room在升級程序中沒有匹配到相應的Migration,為了防止出現升級失敗導致應用程式崩潰的情況,可以在創建資料庫時加入fallbackToDestructiveMigration()方法,該方法能夠在出現升級例外時,重新創建資料表,**需要注意的是,雖然應用程式不會崩潰,但由于資料表被重新創建,所有的資料也將會丟失,**如下所示:
Room.databaseBuilder(
AppUtil.application,
StudentDataBase::class.java,
STUDENT_DB_NAME
)
.addMigrations(MIGRATION_1_2,MIGRATION_2_3)
.fallbackToDestructiveMigration()
.build()
實戰
本例子基于前面預先創建好的學生資料庫表,進行兩次升級,第一次升級增加Fruit表,第二次在Fruit表中新增欄位,可參考Jetpack:Room超詳細使用踩坑指南!Jetpack:Room配合LiveData/Flow使用優化,Room+Flow使用原理決議,
1.創建Fruit表物體類
@Entity(tableName = FRUIT_TABLE_NAME)
data class FruitEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = FRUIT_TABLE_ID)
val id: Int = 0,
@ColumnInfo(name = FRUIT_TABLE_TEXT)
val text: String?
)
/**
* 表名字相關,統一定義.
*/
const val FRUIT_TABLE_NAME = "fruit"
const val FRUIT_TABLE_ID = "fruit_id"
const val FRUIT_TABLE_TEXT = "fruit_name"
2.在資料升級的宣告的Database注解中,新加入Fruit物體類,升級版本為2,
//之前
@Database(entities = arrayOf(StudentEntity::class), version = 1)
abstract class StudentDataBase : RoomDatabase()
//新增FruitEntity物體類 升級版本號
@Database(entities = arrayOf(StudentEntity::class,FruitEntity::class), version = 2)
abstract class StudentDataBase : RoomDatabase()
3.新增migration,同事設定進addMigration方法,
/**
* 資料庫升級 1 到 2
*/
private val MIGRATION_1_2 = object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
//新增 FRUIT 表
database.execSQL("CREATE TABLE IF NOT EXISTS `$FRUIT_TABLE_NAME` (`$FRUIT_TABLE_ID` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `$FRUIT_TABLE_TEXT` TEXT)")
}
}
//修改資料庫宣告,將MIGRATION_1_2 設定進addMigrations
fun getDataBase(): StudentDataBase {
return INSTANT ?: synchronized(this) {
INSTANT ?: Room.databaseBuilder(
AppUtil.application,
StudentDataBase::class.java,
STUDENT_DB_NAME
).addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration()
.build()
.also {
INSTANT = it
}
}
}
寫Dao介面,測驗:
@Dao
interface ConflateDao {
@Query("select * from $FRUIT_TABLE_NAME")
suspend fun obtainFruit() : List<FruitEntity>
@Insert
suspend fun insertFruit(fruitEntity: FruitEntity)
}
//StudentDataBase中獲取Dao
abstract fun getConflateEntityDao():ConflateDao
//activity 測驗代碼
btnTransactionInsertGet.text = "MIGRATION TEST"
btnTransactionInsertGet.setOnClickListener {
val conflateEntityDao = StudentDataBase.getDataBase().getConflateEntityDao()
lifecycleScope.launch {
StudentDataBase.getDataBase().withTransaction {
conflateEntityDao.insertFruit(FruitEntity(text = "apple")
val obtainFruit = conflateEntityDao.obtainFruit()
withContext(Dispatchers.Main.immediate){
binding.text.text = obtainFruit.toString()
}
}
}
}
如此,第一次升級新增表就可以了,
下面將資料庫由2升級到3,在FruitEntity中新增一個欄位,
1.新增text2欄位在FruitEntity中
@ColumnInfo(name = FRUIT_TABLE_OTHER_NAME)
val text2: String?
const val FRUIT_TABLE_OTHER_NAME = "fruit_other_name"
2.StudentDataBase升級為3,新增MIGRATION_2_3,并且加入addMigrations中
//升級version 為3
@Database(entities = arrayOf(StudentEntity::class,FruitEntity::class), version = 3)
abstract class StudentDataBase : RoomDatabase()
//新增MIGRATION_2_3
private val MIGRATION_2_3 = object :Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
//FRUIT 表 新增一列
database.execSQL("ALTER TABLE `$FRUIT_TABLE_NAME` ADD COLUMN `$FRUIT_TABLE_OTHER_NAME` TEXT ")
}
}
//加入addMigrations中
Room.databaseBuilder(
AppUtil.application,
StudentDataBase::class.java,
STUDENT_DB_NAME
)
.addMigrations(MIGRATION_1_2,MIGRATION_2_3)
.fallbackToDestructiveMigration()
.build()
3.測驗代碼修改,多傳入text2欄位的值,
conflateEntityDao.insertFruit(FruitEntity(text = "apple",text2 = "other apple2"))
val obtainFruit = conflateEntityDao.obtainFruit()
withContext(Dispatchers.Main.immediate){
binding.text.text = obtainFruit.toString()
}
這個資料庫由2到3也升級完成了,注意欄位宣告為可空型別,這樣的資料沒有text2欄位,對應的回傳結果為null,
擴展知識
在Sqlite中修改表結構比較麻煩,例如,我們想將Student表中的age欄位型別從INTEGER改為TEXT,
最好的方式是采用銷毀與重建策略,該策略大致分為以下幾個步驟,
- 創建符合要求的臨時表,比如:temp_student.
- 將資料從舊的資料表student值給新的臨時表temp_student.
- 洗掉舊表student.
- 將臨時表temp_student重命名為student.
如下所示:
private val MIGRATION_3_4 = object :Migration(3,4){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `temp_student` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT) ")
database.execSQL("INSERT INTO temp_student (id,name) SELECT id , name FROM student")
database.execSQL("DROP TABLE student")
database.execSQL("ALTER TABLE temp_student RENAME TO student")
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/301901.html
標籤:其他
