在上一篇中,对Room进行了ViewModel+LiveData封装。在本篇中,将会讲解Room对应的升级与预填充。直接开始吧!
如图所示
有时候我们希望应用自带一些数据供我们使用,我们可以讲数据库文件放入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
目录下的,这里就不贴图了。
来看看运行效果(卸载原有的):
可以看出,在软件安装成功时,就预先保留了对应数据!
好了接着下一个!
这里都已经提到了使用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
分别代表对应数据库的升级策略COLUMN sex INTEGER
列名COLUMN bar_data INTEGER
列名@Database(xxx, version =1, xxxz)
这里面的version
表示当前版本,如果要升级2,那么就将最新App对应属性改为2Student
实体类也需要根据对应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遇到跨版本升级时:
Migration(1, 2)
、Migration(2, 3)
以完成升级!这时我们看到,升级数据库只看到过添加列名情况!那万一说,对应列名需要该变量类型该怎么做呢?
比如说原有的sex为INTEGER
,后面突然发病要将sex改为TEXT
呢?
大致分为以下步骤:
看完步骤,思路清晰了,来实现看看!
@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,对应实体类需要对应调整)运行效果
我们看到对应列名类型已经修改成功!
那么在升级时,我们要不要将升级的过程保留下呢?如果能保留,以后排查问题的时候也会方便一点!
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组件!