前言
在上一篇中,对Room进行了ViewModel+LiveData封装。在本篇中,将会讲解Room对应的升级与预填充。直接开始吧!
1、预填充数据库
如图所示
有时候我们希望应用自带一些数据供我们使用,我们可以讲数据库文件放入assets目录一起打包发布,在用户首次打开App时,使用createFromAsset()和createFrimFile()创建Room数据库。
如图所示
既然要自带一些数据,那么我们预先准备一些数据存入自带数据库里面。
那看看如何使用?
@Database(entities = [Student::class], version =1, exportSchema = false)
abstract class MyDatabase : RoomDatabase() {
companion object {
private const val DATABASE_NAME = "my_db.db"
private var mInstance: MyDatabase? = null
@Synchronized
@JvmStatic
open fun getInstance(context: Context): MyDatabase? {
if (mInstance == null) {
mInstance = Room.databaseBuilder(
context.applicationContext,
MyDatabase::class.java,
DATABASE_NAME
) //.allowMainThreadQueries()
.createFromAsset("prestudent.db")
.build()
}
return mInstance
}
abstract fun getStudentDao(): StudentDao?
}
我们可以看到,额外加了一句.createFromAsset("prestudent.db")
,注意这里FromAsset
,也就是说,对应的预存储数据库是存放在Asset
目录下的,这里就不贴图了。
来看看运行效果(卸载原有的):
可以看出,在软件安装成功时,就预先保留了对应数据!
好了接着下一个!
2、使用Migration升级数据库
2.1 只增列名情况
这里都已经提到了使用Migration,那么试试看?
@Database(entities = [Student::class], version =1, exportSchema = false)
abstract class MyDatabase : RoomDatabase() {
companion object {
private const val DATABASE_NAME = "my_db.db"
private var mInstance: MyDatabase? = null
@Synchronized
@JvmStatic
open fun getInstance(context: Context): MyDatabase? {
if (mInstance == null) {
mInstance = Room.databaseBuilder(
context.applicationContext,
MyDatabase::class.java,
DATABASE_NAME
) //.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.createFromAsset("prestudent.db")
.build()
}
return mInstance
}
@JvmStatic
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE student ADD COLUMN sex INTEGER NOT NULL DEFAULT 1")
}
}
@JvmStatic
val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE student ADD COLUMN bar_data INTEGER NOT NULL DEFAULT 1")
}
}
}
abstract fun getStudentDao(): StudentDao?
}
代码解析
-
MIGRATION_1_2
与MIGRATION_2_3
分别代表对应数据库的升级策略 - 1-2版本添加了
COLUMN sex INTEGER
列名 - 2-3版本添加了
COLUMN bar_data INTEGER
列名 -
@Database(xxx, version =1, xxxz)
这里面的version
表示当前版本,如果要升级2,那么就将最新App对应属性改为2 - 注意,对应
Student
实体类也需要根据对应SQL做对应的修改!比如1-2版本添加了sex
,那么实体类也需要哟
实体类就不贴了哈,要根据SQL做对应的调整!
来看看1-2升级的运行效果
升级前对应版本1的数据库信息:
升级后对应版本2的数据库信息:(version=2 ,对应数据表实体类调整)
升级后对应版本3的数据库信息:(version=3 ,对应数据表实体类调整)
OK,测试都没问题!我们现在都是1升2,2升3。那么如果说保留1升2,2升3,没有直接1升3,用户版本就为1,而我们数据库已经升级到3了呢?
我们将版本改为1,对应数据表实体类也回退1状态,卸载原App重新试试!
重新运行后,当前App数据库版本为1,这时,
:(直接将版本改为3,使用版本3的升级策略,version=3 ,对应数据表实体类调整)
我们发现,当Room遇到跨版本升级时:
- Room会先判断有没有从1到3的升级方案,
- 如果有,就直接执行从1到3的升级方案;
- 如果没有,那么Room会按照顺序先后执行
Migration(1, 2)
、Migration(2, 3)
以完成升级!
这时我们看到,升级数据库只看到过添加列名情况!那万一说,对应列名需要该变量类型该怎么做呢?
比如说原有的sex为INTEGER
,后面突然发病要将sex改为TEXT
呢?
2.2 销毁和重建策略
大致分为以下步骤:
- 创建一张符合表结构要求的临时表temp_student
- 将数据从旧表student复制到临时表temp_student
- 删除旧表student
- 将临时表temp_student重命名为student
看完步骤,思路清晰了,来实现看看!
@Database(entities = [Student::class], version =3, exportSchema = false)
abstract class MyDatabase : RoomDatabase() {
companion object {
private const val DATABASE_NAME = "my_db.db"
private var mInstance: MyDatabase? = null
@Synchronized
@JvmStatic
open fun getInstance(context: Context): MyDatabase? {
if (mInstance == null) {
mInstance = Room.databaseBuilder(
context.applicationContext,
MyDatabase::class.java,
DATABASE_NAME
) //.allowMainThreadQueries()
// .fallbackToDestructiveMigration()
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.createFromAsset("prestudent.db")
.build()
}
return mInstance
}
@JvmStatic
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE student ADD COLUMN sex INTEGER NOT NULL DEFAULT 1")
}
}
@JvmStatic
val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE student ADD COLUMN bar_data INTEGER NOT NULL DEFAULT 1")
}
}
@JvmStatic
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'," +
"bar_data INTEGER NOT NULL DEFAULT 1)"
)
database.execSQL(
"INSERT INTO temp_student (name,age,sex,bar_data)" +
"SELECT name,age,sex,bar_data FROM student"
)
database.execSQL("DROP TABLE student")
database.execSQL("ALTER TABLE temp_student RENAME TO student")
}
}
}
abstract fun getStudentDao(): StudentDao?
}
这时,我们看到,创建了MIGRATION_3_4
,里面的逻辑就是上面我们理清的步骤,那么?
将版本改为4(version=4,对应实体类需要对应调整)运行效果
我们看到对应列名类型已经修改成功!
那么在升级时,我们要不要将升级的过程保留下呢?如果能保留,以后排查问题的时候也会方便一点!
3、Schema文件
Room在每次数据库升级过程中,都会导出一个Schema文件,这是一个json格式的文件,其中包含了数据库的基本信息,有了该文件,开发者能清楚的知道数据可的厉次变更过程,极大方便了开发者排查问题!
那么该怎么使用呢?
android {
compileSdkVersion 30
defaultConfig {
...略
// javaCompileOptions {
// annotationProcessorOptions {
// arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]//指定数据库schema导出的位置
// }
// }
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas".toString())
}
}
}
}
注释部分是业务逻辑使用Java时,对应配置导出的位置!下者为Kotlin对应配置导出位置!
此外还需要将
@Database(entities = [Student::class], version =1, exportSchema = false){
}
对应的 exportSchema
改为true
(默认为true),卸载重新从版本1开始运行!
运行成功后,修改版本为2(version=2,对应实体类调整)重新运行后:
OK,我们看到左边已经有对应的新文件!打开看,就是对应的升级策略!
结束语
好了,本篇到这里就结束了,相信你对Room有了全面的理解!在下一篇中,将会讲解Jetpack对应的Navigation组件!