Jetpack架构组件Room-数据库升级
基础使用
使用环境
androidx、kotlin
注入依赖
def room_version = "2.2.6"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin扩展和协同程序对Room的支持
implementation "androidx.room:room-ktx:$room_version"
注意:需要引用kapt插件
新增表
1、新增一个实体类,并标注
/**
* 作者: CQ
* 日期: 2021-05-11
* 说明: 学生
*/
@Entity
class Student(
@PrimaryKey(autoGenerate = true)
var sId: Int,
@ColumnInfo(name = "name1")
var name: String,
@ColumnInfo(name = "age")
var age: Int
) {
override fun toString(): String {
return "Student(sId=$sId, name='$name', age=$age)"
}
}
2、声明新增的表(不推荐,不用跟着写,建议使用3)
/**
* 作者: CQ
* 日期: 2021-05-13
* 说明: 继承RoomDatabase类
*/
// 这里声明了表(Student::class),
// version数据库版本原先是1,改成2(也可以改成3,4,只要是大于上个版本的值就好,但是建议是一级一级的加,方便管理)
@Database(entities = [Teacher::class, Student::class], version = 2, exportSchema = false)
abstract class AppDataRoom : RoomDatabase() {
// 定义一个操作类
abstract fun teacherDao(): TeacherDao
companion object {
// 使用单例模式
private var instance: AppDataRoom? = null
fun getInstance(context: Context): AppDataRoom {
return instance ?: synchronized(this) {
instance ?: buildDataBase(context)
}
}
private fun buildDataBase(context: Context): AppDataRoom {
return Room
.databaseBuilder(context, AppDataRoom::class.java, "room") // room:数据库名称
.allowMainThreadQueries() // 允许在主线程中操作,如果不加的话,只能在子线程中调用
.fallbackToDestructiveMigration() // 清空数据库中的数据后重建表,这只是升级数据库的方法之一,并不推荐(原有数据都被清空了)
.build()
}
}
}
/**
* 注意:
* 1、在entities中声明需要添加的表(Student::class)
* 2、version数据库版本原先是1,改成2(也可以改成3,4,只要是大于上个版本的值就好,但是建议是一级一级的加,方便管理)
* 3、fallbackToDestructiveMigration():如果设备上的数据库版本与最新架构版本不匹配,可以调用此方法来更改此行为以重新创建数据库。
* 这只是升级数据库的方法之一,并不推荐(原有数据都被清空了)
*/
其实使用方法fallbackToDestructiveMigration()升级,并不推荐,虽然达到了目的,但数据都没了,感觉不能接受,结果如下:
3、合并升级
RoomDatabase类提供了addMigrations(@NonNull Migration… migrations)方法,所以只需要定义一个合并的Migration并添加进去就好了,下面所有数据库升级,都是使用这个方法来定义的
/**
* 自定义一个合并的方法
* 新增表
*/
val MIGRATION_1_2 = object : Migration(1 ,2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS Student(sId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name1 TEXT, age INTEGER NOT NULL)")
}
}
注意: database.execSQL(“CREATE TABLE IF NOT EXISTS Student(sId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name1 TEXT, age INTEGER NOT NULL)”)
这里的sId、name1、age和实体类Student的定义的变量一一对应,而且实体类里的变量默认非空,所以sId 和age这两个字段需要使用NOT NULL来表示。如果声明可为null的话,就像name1一样,只添加字段类型就可以了。
毕竟自动转换java语言,难免会一板一眼的要求。当然,这都是小问题
如果没有注意到非空或者字段不正确,就会出现如下报错:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.roomdemo/com.example.roomdemo.MainActivity}: java.lang.IllegalStateException: Migration didn't properly handle: Student(com.example.roomdemo.room.Student).
Expected:
TableInfo{name='Student', columns={name1=Column{name='name1', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, age=Column{name='age', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, sId=Column{name='sId', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='Student', columns={name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, age=Column{name='age', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, sId=Column{name='sId', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=null}
原报错的合并方法代码如下:
/**
* 自定义一个合并的方法
*/
val MIGRATION_1_2 = object : Migration(1 ,2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS Student(sId INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)")
}
}
其实报错信息给出的提示已经很明显了,在我们创建实体类表的时候,默认的表字段都是非空的,所以在写SQL的时候需要在非空字段声明NOT NULL。
4、添加到操作类里
Room
.databaseBuilder(context, AppDataRoom::class.java, "room") // room:数据库名称
.allowMainThreadQueries() // 允许在主线程中操作,如果不加的话,只能在子线程中调用
.addMigrations(MIGRATION_1_2)
// .fallbackToDestructiveMigration() // 等于删除数据库后重建表,这只是升级数据库的方法之一,并不推荐(原有数据都被清空了)
.build()
新增字段
1、实体类Student新增一个字段mark
/**
* 作者: CQ
* 日期: 2021-05-11
* 说明: 学生
*/
@Entity
class Student(
@PrimaryKey(autoGenerate = true)
var sId: Int,
@ColumnInfo(name = "name1")
var name: String?,
@ColumnInfo(name = "age")
var age: Int,
@ColumnInfo(name = "mark")
var mark: Int
) {
override fun toString(): String {
return "Student(sId=$sId, name='$name', age=$age)"
}
}
2、修改版本号为3
3、新建一个合并方法并添加
/**
* 新增字段
*/
val MIGRATION_2_3 = object : Migration(2 ,3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Student add COLUMN mark INTEGER NOT NULL DEFAULT 0")
}
}
// 使用如下
addMigrations(MIGRATION_1_2, MIGRATION_2_3)
注意:database.execSQL(“ALTER TABLE Student add COLUMN mark INTEGER NOT NULL DEFAULT 0”)
增加一个非空字段,需要给一个默认值,不然会报如下错误
Caused by: android.database.sqlite.SQLiteException: Cannot add a NOT NULL column with default value NULL (code 1): ,
while compiling: ALTER TABLE Student add COLUMN mark INTEGER NOT NULL
4、查询后,如下:
// 添加数据,查询并打印
val studentDao = AppDataRoom.getInstance(this).studentDao()
studentDao.insert(Student(1, "小名1", 11, 100))
studentDao.insert(Student(2, "小名2", 11, 99))
studentDao.getAll().forEach { Log.e("TAG", "studentDao: $it") }
// 打印结果
TAG: studentDao: Student(sId=1, name='小名1', age=11, mark=100)
TAG: studentDao: Student(sId=2, name='小名2', age=11, mark=99)
修改字段名称
1、实体类Student先修改一下字段名称:mark改成了marks,如果没有使用name = ‘mark’,则需要改变量var mark: Int为var marks: Int
@ColumnInfo(name = "marks")
var mark: Int
2、修改版本号为4
3、定义一个升级方法
/**
* 修改字段名称
*/
val MIGRATION_3_4 = object : Migration(3 ,4) {
override fun migrate(database: SupportSQLiteDatabase) {
// 创建一个新表:表名不同,字段都和Student修改后的字段相同
database.execSQL("CREATE TABLE IF NOT EXISTS Students(sId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name1 TEXT, age INTEGER NOT NULL, marks INTEGER NOT NULL)")
// 旧表的数据复制到新表中
database.execSQL("insert into Students(sId, name1, age, marks) select sId, name1, age, mark from Student")
// 删除旧表
database.execSQL("drop table Student")
// 修改新表名称为旧表名称
database.execSQL("alter table Students rename to Student")
}
}
// 使用如下
addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
4、调用后修改成功后的表如下:
修改字段类型
1、修改实体类变量类型
@Entity
class Student(
@PrimaryKey(autoGenerate = true)
var sId: Int,
@ColumnInfo(name = "name1")
var name: String?,
@ColumnInfo(name = "age")
var age: Int,
@ColumnInfo(name = "marks")
var mark: String
) {
override fun toString(): String {
return "Student(sId=$sId, name='$name', age=$age, marks=$mark)"
}
}
2、修改版本号为5
3、定义一个升级方法
/**
* 修改字段类型
*/
val MIGRATION_4_5 = object : Migration(4 ,5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS Students(sId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name1 TEXT, age INTEGER NOT NULL, marks INTEGER NOT NULL)")
database.execSQL("insert into Students(sId, name1, age, marks) select sId, name1, age, mark from Student")
database.execSQL("drop table Student")
database.execSQL("alter table Students rename to Student")
}
}
调用方法
addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
4、运行后,打开数据库可以到marks字段类型已改变
删除字段
1、删除实体类里的变量mark
@Entity
class Student(
@PrimaryKey(autoGenerate = true)
var sId: Int,
@ColumnInfo(name = "name1")
var name: String?,
@ColumnInfo(name = "age")
var age: Int
) {
override fun toString(): String {
return "Student(sId=$sId, name='$name', age=$age)"
}
}
2、修改版本号为6
3、定义一个升级方法
/**
* 修改字段类型
*/
val MIGRATION_5_6 = object : Migration(5 ,6) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS Students(sId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name1 TEXT, age INTEGER NOT NULL)")
database.execSQL("insert into Students(sId, name1, age) select sId, name1, age from Student")
database.execSQL("drop table Student")
database.execSQL("alter table Students rename to Student")
}
}
调用方法
addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
4、修改后的数据如下:
增删改差,大致如此。如有错误,还请指正,谢谢!