文章目录
- 一、销毁 和 重建策略
- 二、销毁 和 重建策略 核心要点
- 1、创建 Migration 迁移类 – 重点
- 2、配置 Migration 迁移类
- 3、修改数据库版本号
- 4、修改实体类数据类型
- 三、完整代码示例
- 1、数据库版本 3 代码示例
- RoomDatabase 数据库类完整代码
- Student 实体类完整代码
- 执行结果
- 2、数据库版本 4 代码示例
- RoomDatabase 数据库类完整代码
- Student 实体类代码示例
- 执行结果
一、销毁 和 重建策略
在 Android 中使用 Room 操作 SQLite 数据库 , 如果 SQLite 数据库表 修改比较繁琐 ,
如 : 涉及到 修改 数据库表字段的数据类型 , 需要逐个修改数据库值 ;
该环境下 使用 销毁 和 重建策略 是 最佳的方案 ;
销毁 和 重建策略 执行步骤 : 以 Table 表为例 , 要对 Table 表中的数据进行繁琐的操作 ;
- 首先 , 创建一张 符合 新数据库表结构 的 临时数据库表 Temp_Table 表 ;
- 然后 , 将 旧数据库表 Table 表中的数据 拷贝到 临时数据库表 Temp_Table 表中 , 如果需要修改 , 也在该步骤中进行修改 ;
- 再后 , 删除旧的数据库表 Table 表 ;
- 最后 , 将 临时数据库表 Temp_Table 表 重命名为 Table 表 ;
二、销毁 和 重建策略 核心要点
1、创建 Migration 迁移类 – 重点
在本篇博客中 , 在之前的博客
- 【Jetpack】Room + ViewModel + LiveData 综合使用 ( 核心要点说明 | 组合方式 | 代码示例 )
- 【Jetpack】使用 Room 中的 Migration 升级数据库 ( 修改 Entity 实体类 – 更改数据模型 | 创建 Migration 迁移类 | 修改数据库版本 | 代码示例 )
基础上 , 升级数据库版本 4 ;
数据库 版本 2 和 3 分别在 数据库 版本 1 的基础上新增了一个字段 ;
这里要升级的数据库版本 4 , 要 在 数据库版本 3 的基础上 , 将 integer 类型的字段 sex 的 数据类型 修改为 text 类型 ,
这就需要将 整个数据库表中的数据的 指定字段 需要重新赋值 ;
这就需要 使用 销毁重建 策略 ;
销毁 和 重建策略 执行步骤 :
- 首先 , 创建一张 符合 新数据库表结构 的 临时数据库表;
// 创新临时数据库database.execSQL("CREATE TABLE temp_student (" +"id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +"name TEXT," +"age INTEGER NOT NULL," +"sex TEXT DEFAULT 'M'," +"degree INTEGER NOT NULL DEFAULT 1)")
- 然后 , 将 旧数据库表 中的数据 拷贝到 临时数据库表 中 , 如果需要修改 , 也在该步骤中进行修改 ;
// 拷贝数据database.execSQL("INSERT INTO temp_student (name,age,degree)" +"SELECT name,age,degree FROM student")
- 再后 , 删除旧的数据库表 ;
// 删除原始表database.execSQL("DROP TABLE student")
- 最后 , 将 临时数据库表 重命名为 原来的表名 ;
// 将临时表命令为原表表明database.execSQL("ALTER TABLE temp_student RENAME TO student")
上述的 四个步骤 , 都在 Migration 的 public void migrate(@NonNull SupportSQLiteDatabase database) 函数中完成 , 每个步骤分别对应一个 SQL 语句 ;
最终定义的 Migration 为 :
/** * 数据库版本 3 升级到 版本 4 的迁移类实例对象 * 销毁重建策略 */val MIGRATION_3_4: Migration = object : Migration(3, 4) {override fun migrate(database: SupportSQLiteDatabase) {// 创新临时数据库database.execSQL("CREATE TABLE temp_student (" +"id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +"name TEXT," +"age INTEGER NOT NULL," +"sex TEXT DEFAULT 'M'," +"degree INTEGER NOT NULL DEFAULT 1)")// 拷贝数据database.execSQL("INSERT INTO temp_student (name,age,degree)" +"SELECT name,age,degree FROM student")// 删除原始表database.execSQL("DROP TABLE student")// 将临时表命令为原表表明database.execSQL("ALTER TABLE temp_student RENAME TO student")}}
2、配置 Migration 迁移类
在 创建 RoomDatabase.Builder 时 , 调用 RoomDatabase.Builder#addMigrations , 设置上述创建的 销毁重建策略的 Migration 迁移类 ;
fun inst(context: Context): StudentDatabase {if (!::instance.isInitialized) {synchronized(StudentDatabase::class) {// 创建数据库instance = Room.databaseBuilder(context.applicationContext,StudentDatabase::class.java,"student_database.db").addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).fallbackToDestructiveMigration().allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库// 如果要在主线程操作数据库需要调用该函数.build()}}return instance;}
3、修改数据库版本号
在 RoomDatabase 类的 @Database 注解上的 version 数据库版本号参数设置为 4 ;
@Database(entities = [Student::class], version = 4, exportSchema = true)abstract class StudentDatabase: RoomDatabase() {
4、修改实体类数据类型
将 Entity 实体类 Student 类中的 sex 字段 由
/** * 性别字段 * 数据库表中的列名为 sex * 数据库表中的类型为 INTEGER 文本类型 */@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER)var sex: Int = 0
修改为 :
/** * 性别字段 * 数据库表中的列名为 sex * 数据库表中的类型为 TEXT 文本类型 */@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.TEXT)var sex: String = "M"
三、完整代码示例
代码地址 : https://github.com/han1202012/Room_ViewModel_LiveData
1、数据库版本 3 代码示例
RoomDatabase 数据库类完整代码
package kim.hsl.rvlimport android.content.Contextimport android.util.Logimport androidx.room.Databaseimport androidx.room.Roomimport androidx.room.RoomDatabaseimport androidx.room.migration.Migrationimport androidx.sqlite.db.SupportSQLiteDatabase@Database(entities = [Student::class], version = 3, exportSchema = true)abstract class StudentDatabase: RoomDatabase() {/** * 获取 数据库访问 对象 * 这是必须要实现的函数 */abstract fun studentDao(): StudentDaocompanion object {lateinit var instance: StudentDatabase/** * 数据库版本 1 升级到 版本 2 的迁移类实例对象 */val MIGRATION_1_2: Migration = object : Migration(1, 2) {override fun migrate(database: SupportSQLiteDatabase) {Log.i("Room_StudentDatabase", "数据库版本 1 升级到 版本 2")database.execSQL("alter table student add column sex integer not null default 1")}}/** * 数据库版本 2 升级到 版本 3 的迁移类实例对象 */val MIGRATION_2_3: Migration = object : Migration(2, 3) {override fun migrate(database: SupportSQLiteDatabase) {Log.i("Room_StudentDatabase", "数据库版本 2 升级到 版本 3")database.execSQL("alter table student add column degree integer not null default 1")}}fun inst(context: Context): StudentDatabase {if (!::instance.isInitialized) {synchronized(StudentDatabase::class) {// 创建数据库instance = Room.databaseBuilder(context.applicationContext,StudentDatabase::class.java,"student_database.db").addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).fallbackToDestructiveMigration().allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库// 如果要在主线程操作数据库需要调用该函数.build()}}return instance;}}}
Student 实体类完整代码
package kim.hsl.rvlimport androidx.room.ColumnInfoimport androidx.room.Entityimport androidx.room.Ignoreimport androidx.room.PrimaryKey/** * 定义数据库表 Entity 实体 / 同时定义数据库表 和 对鹰的实体类 * 设置该数据类对应数据库中的一张数据表, 表名为 student * 该数据库表中的数据对应一个 Student 类实例对象 */@Entity(tableName = "student")class Student {/** * @PrimaryKey 设置主键 autoGenerate 为自增 * @ColumnInfo name 设置列名称 / typeAffinity 设置列类型 */@PrimaryKey(autoGenerate = true)@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)var id: Int = 0/** * 姓名字段 * 数据库表中的列名为 name * 数据库表中的类型为 TEXT 文本类型 */@ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)lateinit var name: String/** * 年龄字段 * 数据库表中的列名为 age * 数据库表中的类型为 INTEGER 文本类型 */@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)var age: Int = 0/** * 性别字段 * 数据库表中的列名为 sex * 数据库表中的类型为 INTEGER 文本类型 */@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER)var sex: Int = 0/** * degree字段 * 数据库表中的列名为 sex * 数据库表中的类型为 INTEGER 文本类型 */@ColumnInfo(name = "degree", typeAffinity = ColumnInfo.INTEGER)var degree: Int = 0/** * 有些属性用于做业务逻辑 * 不需要插入到数据库中 * 使用 @Ignore 注解修饰该属性字段 */@Ignorelateinit var studentInfo: String/** * 默认的构造方法给 Room 框架使用 */constructor(id: Int, name: String, age: Int) {this.id = idthis.name = namethis.age = age}/** * 使用 @Ignore 标签标注后 * Room 就不会使用该构造方法了 * 这个构造方法是给开发者使用的 */@Ignoreconstructor(name: String, age: Int) {this.name = namethis.age = age}/** * 使用 @Ignore 标签标注后 * Room 就不会使用该构造方法了 * 这个构造方法是给开发者使用的 */@Ignoreconstructor(id: Int) {this.id = id}override fun toString(): String {return "Student(id=$id, name='$name', age=$age)"}}
执行结果
先执行数据库版本为 3 的应用 , 输出结果如下 :
2023-06-05 19:17:12.251 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: []2023-06-05 19:17:12.532 I/Room_MainActivity: 插入数据 S1 : Student(id=0, name='Tom', age=18)2023-06-05 19:17:12.536 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18)]2023-06-05 19:17:13.036 I/Room_MainActivity: 插入数据 S2 : Student(id=0, name='Jerry', age=16)2023-06-05 19:17:13.038 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name='Jerry', age=16)]2023-06-05 19:17:13.538 I/Room_MainActivity: 更新数据 S2 : Student(id=2, name='Jack', age=60)2023-06-05 19:17:13.540 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name='Jack', age=60)]2023-06-05 19:17:14.051 I/Room_MainActivity: 删除数据 id = 12023-06-05 19:17:14.060 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=2, name='Jack', age=60)]2023-06-05 19:17:14.553 I/Room_MainActivity: 主动查询 : LiveData : androidx.room.RoomTrackingLiveData@8726677 , 实际数据 : null2023-06-05 19:17:14.554 I/Room_MainActivity: 主动查询2 : [Student(id=2, name='Jack', age=60)]
2、数据库版本 4 代码示例
将数据库版本修改为 4 , 然后执行上述 销毁重建策略 修改 ;
主要是创建了 数据库版本 3 升级到 版本 4 的迁移类实例对象 , 该 Migration 类
RoomDatabase 数据库类完整代码
package kim.hsl.rvlimport android.content.Contextimport android.util.Logimport androidx.room.Databaseimport androidx.room.Roomimport androidx.room.RoomDatabaseimport androidx.room.migration.Migrationimport androidx.sqlite.db.SupportSQLiteDatabase@Database(entities = [Student::class], version = 4, exportSchema = true)abstract class StudentDatabase: RoomDatabase() {/** * 获取 数据库访问 对象 * 这是必须要实现的函数 */abstract fun studentDao(): StudentDaocompanion object {lateinit var instance: StudentDatabase/** * 数据库版本 1 升级到 版本 2 的迁移类实例对象 */val MIGRATION_1_2: Migration = object : Migration(1, 2) {override fun migrate(database: SupportSQLiteDatabase) {Log.i("Room_StudentDatabase", "数据库版本 1 升级到 版本 2")database.execSQL("alter table student add column sex integer not null default 1")}}/** * 数据库版本 2 升级到 版本 3 的迁移类实例对象 */val MIGRATION_2_3: Migration = object : Migration(2, 3) {override fun migrate(database: SupportSQLiteDatabase) {Log.i("Room_StudentDatabase", "数据库版本 2 升级到 版本 3")database.execSQL("alter table student add column degree integer not null default 1")}}/** * 数据库版本 3 升级到 版本 4 的迁移类实例对象 * 销毁重建策略 */val MIGRATION_3_4: Migration = object : Migration(3, 4) {override fun migrate(database: SupportSQLiteDatabase) {Log.i("Room_StudentDatabase", "数据库版本 3 升级到 版本 4")// 创新临时数据库database.execSQL("CREATE TABLE temp_student (" +"id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +"name TEXT," +"age INTEGER NOT NULL," +"sex TEXT NOT NULL DEFAULT 'M'," +"degree INTEGER NOT NULL DEFAULT 1)")// 拷贝数据database.execSQL("INSERT INTO temp_student (name, age, degree)" +"SELECT name, age, degree FROM student")// 删除原始表database.execSQL("DROP TABLE student")// 将临时表命令为原表表明database.execSQL("ALTER TABLE temp_student RENAME TO student")}}fun inst(context: Context): StudentDatabase {if (!::instance.isInitialized) {synchronized(StudentDatabase::class) {// 创建数据库instance = Room.databaseBuilder(context.applicationContext,StudentDatabase::class.java,"student_database.db").addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).fallbackToDestructiveMigration().allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库// 如果要在主线程操作数据库需要调用该函数.build()}}return instance;}}}
Student 实体类代码示例
package kim.hsl.rvlimport androidx.room.ColumnInfoimport androidx.room.Entityimport androidx.room.Ignoreimport androidx.room.PrimaryKey/** * 定义数据库表 Entity 实体 / 同时定义数据库表 和 对鹰的实体类 * 设置该数据类对应数据库中的一张数据表, 表名为 student * 该数据库表中的数据对应一个 Student 类实例对象 */@Entity(tableName = "student")class Student {/** * @PrimaryKey 设置主键 autoGenerate 为自增 * @ColumnInfo name 设置列名称 / typeAffinity 设置列类型 */@PrimaryKey(autoGenerate = true)@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)var id: Int = 0/** * 姓名字段 * 数据库表中的列名为 name * 数据库表中的类型为 TEXT 文本类型 */@ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)lateinit var name: String/** * 年龄字段 * 数据库表中的列名为 age * 数据库表中的类型为 INTEGER 文本类型 */@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)var age: Int = 0/** * 性别字段 * 数据库表中的列名为 sex * 数据库表中的类型为 TEXT 文本类型 */@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.TEXT)var sex: String = "M"/** * 性别字段 * 数据库表中的列名为 sex * 数据库表中的类型为 INTEGER 文本类型 *//*@ColumnInfo(name = "sex", typeAffinity = ColumnInfo.INTEGER)var sex: Int = 0*//** * degree字段 * 数据库表中的列名为 sex * 数据库表中的类型为 INTEGER 文本类型 */@ColumnInfo(name = "degree", typeAffinity = ColumnInfo.INTEGER)var degree: Int = 0/** * 有些属性用于做业务逻辑 * 不需要插入到数据库中 * 使用 @Ignore 注解修饰该属性字段 */@Ignorelateinit var studentInfo: String/** * 默认的构造方法给 Room 框架使用 */constructor(id: Int, name: String, age: Int) {this.id = idthis.name = namethis.age = age}/** * 使用 @Ignore 标签标注后 * Room 就不会使用该构造方法了 * 这个构造方法是给开发者使用的 */@Ignoreconstructor(name: String, age: Int) {this.name = namethis.age = age}/** * 使用 @Ignore 标签标注后 * Room 就不会使用该构造方法了 * 这个构造方法是给开发者使用的 */@Ignoreconstructor(id: Int) {this.id = id}override fun toString(): String {return "Student(id=$id, name='$name', age=$age)"}}
执行结果
执行结果如下 :
2023-06-05 19:18:58.864 I/Room_StudentDatabase: 数据库版本 3 升级到 版本 42023-06-05 19:18:58.909 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Jack', age=60)]2023-06-05 19:18:59.346 I/Room_MainActivity: 插入数据 S1 : Student(id=0, name='Tom', age=18)2023-06-05 19:18:59.351 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Jack', age=60), Student(id=2, name='Tom', age=18)]2023-06-05 19:18:59.853 I/Room_MainActivity: 插入数据 S2 : Student(id=0, name='Jerry', age=16)2023-06-05 19:18:59.855 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Jack', age=60), Student(id=2, name='Tom', age=18), Student(id=3, name='Jerry', age=16)]2023-06-05 19:19:00.362 I/Room_MainActivity: 更新数据 S2 : Student(id=2, name='Jack', age=60)2023-06-05 19:19:00.371 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Jack', age=60), Student(id=2, name='Jack', age=60), Student(id=3, name='Jerry', age=16)]2023-06-05 19:19:00.875 I/Room_MainActivity: 删除数据 id = 12023-06-05 19:19:00.877 I/Room_MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=2, name='Jack', age=60), Student(id=3, name='Jerry', age=16)]2023-06-05 19:19:01.377 I/Room_MainActivity: 主动查询 : LiveData : androidx.room.RoomTrackingLiveData@b957950 , 实际数据 : null2023-06-05 19:19:01.378 I/Room_MainActivity: 主动查询2 : [Student(id=2, name='Jack', age=60), Student(id=3, name='Jerry', age=16)]