ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

Jetpack:Room数据库升级详解实战!

2021-09-20 13:02:30  阅读:641  来源: 互联网

标签:Room val Jetpack 升级 FRUIT MIGRATION TABLE 详解


系列文章目录

相关文章:
Jetpack:Room超详细使用踩坑指南!
Jetpack:Room+kotlin协程? 事务问题分析,withTransaction API 详解.
Jetpack:Room使用报错FAQ
Jetpack:Room配合LiveData/Flow使用优化,Room+Flow使用原理解析。


文章目录


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。

最好的方式是采用销毁与重建策略,该策略大致分为以下几个步骤。

  1. 创建符合要求的临时表,比如:temp_student.
  2. 将数据从旧的数据表student值给新的临时表temp_student.
  3. 删除旧表student.
  4. 将临时表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")
	}
}

标签:Room,val,Jetpack,升级,FRUIT,MIGRATION,TABLE,详解
来源: https://blog.csdn.net/weixin_44235109/article/details/120390052

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有