Jetpack:Room数据库升级详解实战!
2021/9/20 19:27:20
本文主要是介绍Jetpack:Room数据库升级详解实战!,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
系列文章目录
相关文章:
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") } }
这篇关于Jetpack:Room数据库升级详解实战!的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-11国产医疗级心电ECG采集处理模块
- 2025-01-10Rakuten 乐天积分系统从 Cassandra 到 TiDB 的选型与实战
- 2025-01-09CMS内容管理系统是什么?如何选择适合你的平台?
- 2025-01-08CCPM如何缩短项目周期并降低风险?
- 2025-01-08Omnivore 替代品 Readeck 安装与使用教程
- 2025-01-07Cursor 收费太贵?3分钟教你接入超低价 DeepSeek-V3,代码质量逼近 Claude 3.5
- 2025-01-06PingCAP 连续两年入选 Gartner 云数据库管理系统魔力象限“荣誉提及”
- 2025-01-05Easysearch 可搜索快照功能,看这篇就够了
- 2025-01-04BOT+EPC模式在基础设施项目中的应用与优势
- 2025-01-03用LangChain构建会检索和搜索的智能聊天机器人指南