room 数据库有三个比较重要的部分java
而后build后,会自动生成 继承AppDataBase 的 AppDataBase_Impl 类,并自动实现全部的抽象方法android
@Database(entities = [User::class, Course::class, Teacher::class, UserJoinCourse::class, IDCard::class], version = 1)
abstract class AppDataBase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun teacherDao(): TeacherDao
abstract fun courseDao(): CourseDao
abstract fun userJoinCourseDao(): UserJoinCourseDao
abstract fun idCardDao(): IDCardDao
}
复制代码
而后build后,会自动生成 继承 UserDao 的 UserDao_Impl 类,并自动实现全部的抽象方法git
@Dao
abstract class UserDao {
@Query("select * from tab_user")
abstract fun getAll(): List<User>
@Query("select * from tab_user where uid = :uid")
abstract fun getById(uid: Long): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(vararg users: User): LongArray
@Update
abstract fun update(user: User)
@Delete
abstract fun delete(vararg users: User): Int
}
复制代码
@Entity(tableName = "tab_user")
data class User(
@ColumnInfo(name = "uid") // 定义列的名字
@PrimaryKey(autoGenerate = true) // 标示主键,并自增加
var uid: Long?,
@ColumnInfo // 若是没有指定列的名字,则使用字段名称
var username: String?,
// @ColumnInfo 是非必须的,room 默认会将全部class的全部字段定义table的列
var age: Int = 0
)
复制代码
若是你须要使用多个字段一块儿看成主键,则须要使用@Entity注解中的primaryKeys属性定义联合主键github
@Entity(primaryKeys = arrayOf("firstName", "lastName"))
data class User(
val firstName: String?,
val lastName: String?
)
复制代码
可使用@Ignore注解sql
@Entity(primaryKeys = arrayOf("firstName", "lastName"))
data class User(
val firstName: String?,
val lastName: String?,
@Ignore val picture: Bitmap?
)
复制代码
也可使用@Entity注解中的ignoredColumns属性数据库
@Entity(
primaryKeys = ["firstName", "lastName"],
ignoredColumns = ["picture"]
)
data class User(
val firstName: String?,
val lastName: String?,
val picture: Bitmap?
)
复制代码
使用@Entity注解中的indices属性添加索引api
@Entity(indices = [Index(value = ["lastName", "address"])])
data class User(
@PrimaryKey val id: Int,
val firstName: String?,
val address: String?,
val lastName: String?
)
复制代码
使用@Entity注解中的foreignKeys属性能够定义两个表之间的外键关联app
@Entity(tableName = "tab_course")
data class Course(
@ColumnInfo(name = "cid") @PrimaryKey(autoGenerate = true) var cid: Long? = null,
@ColumnInfo var name: String
)
@Entity(tableName = "tab_teacher", foreignKeys = [ForeignKey(
entity = Course::class,
childColumns = ["cid"], // tab_teacher的列名
parentColumns = ["cid"] // 关联的tab_course表的主键列名
)], indices = [Index("cid")]
)
data class Teacher(
@ColumnInfo(name = "tid") @PrimaryKey(autoGenerate = true) var tid: Long? = null,
var name: String,
var cid: Long? = null
)
复制代码
使用 外键关联 + 惟一约束 表示 一对一的关联关系ide
好比:一个用户只能有一张身份证,一张身份证只能被一个用户拥有函数
// 定义user表
@Entity(tableName = "tab_user")
data class User(
@ColumnInfo(name = "uid")
@PrimaryKey(autoGenerate = true)
var uid: Long?,
@ColumnInfo
var username: String?,
var age: Int = 0
)
// 定义身份证表,并与user表创建一对一的关联关系
@Entity(
tableName = "tab_id_card",
foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = ["uid"],
childColumns = ["uid"]
)],
indices = [
Index("_uuid", unique = true),
Index("uid", unique = true) // 标示惟一约束,则与tab_user是一对一的关系
]
)
class IDCard(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "_uuid")
var uuid: String,
var startTime: String,
var expireTime: String,
@ColumnInfo(name = "uid")
var userId: Long?
)
复制代码
我的建议为了方便操做,通常在user表下定一个用 @Ignore 注解的idCard字段,好比:
@Entity(tableName = "tab_user")
data class User(
@ColumnInfo(name = "uid")
@PrimaryKey(autoGenerate = true)
var uid: Long?,
@ColumnInfo
var username: String?,
var age: Int = 0
) {
//为了方便操做,因此定义一个idCard字段
@Ignore var idCard: IDCard? = null
}
复制代码
而后UserDao的实现以下:
@Dao
abstract class UserDao {
@Query("select * from tab_user where uid = :uid")
abstract fun getById(uid: Long): User?
// 查询user时,同时也查询idcard
fun getByIdWithIdCard(uid: Long): User? {
val user = getById(uid)
user?.let {
it.idCard = AppDataBase.getInstance().idCardDao().getByForeignKey(it.uid!!)
}
return user
}
}
@Dao
abstract class IDCardDao {
@Query("select * from tab_id_card where uid in (:uid)")
abstract fun getByForeignKey(uid: Long): IDCard?
...
}
复制代码
使用 外键关联 表示 一对多的关联关系
好比:一个老师只能教一门 课程,一门课程 能够 被多个老师教
// 定义课程table
@Entity(tableName = "tab_course")
data class Course(
@ColumnInfo(name = "cid") @PrimaryKey(autoGenerate = true) var cid: Long? = null,
@ColumnInfo var name: String
) {
// 同理 为了 方便操做,定义一个teachers字段
@Ignore
var teachers: List<Teacher>? = null
}
//定义老师table,并与tab_course创建一对多的关系
@Entity(tableName = "tab_teacher", foreignKeys = [ForeignKey(
entity = Course::class,
childColumns = ["cid"],
parentColumns = ["cid"]
)], indices = [Index("cid")]
)
data class Teacher(
@ColumnInfo(name = "tid") @PrimaryKey(autoGenerate = true) var tid: Long? = null,
var name: String,
var cid: Long? = null
) {
// 同理 为了 方便操做,定义一个course字段
@Ignore
var course: Course? = null
}
复制代码
而后为了方便操做,CourseDao 和 TeacherDao的实现以下:
@Dao
abstract class CourseDao {
@Query("select * from tab_course where cid = :cid")
abstract fun getById(cid: Long): Course?
fun getByIdWithTeacher(cid: Long): Course? {
return getById(cid)?.apply {
this.teachers = AppDataBase.getInstance().teacherDao().getByForeignKey(this.cid!!)
this.teachers?.forEach {
it.course = this
}
}
}
...
}
@Dao
abstract class TeacherDao {
@Query("select * from tab_teacher where tid = :tid")
abstract fun getById(tid: Long): Teacher?
@Query("select * from tab_teacher where cid = :cid")
abstract fun getByForeignKey(cid: Long): List<Teacher>
...
}
复制代码
多对多的关联关系,须要一个中间表来表示
好比:一个user能够学多门课程,一门课程也能够被多个user学习
中间表结构以下:
@Entity(
tableName = "tab_user_join_course",
indices = [Index(value = ["uid", "cid"], unique = true)],
foreignKeys = [
// 外键关联user表
ForeignKey(entity = User::class, childColumns = ["uid"], parentColumns = ["uid"], onDelete = ForeignKey.CASCADE),
// 外键关联课程表
ForeignKey(entity = Course::class, childColumns = ["cid"], parentColumns = ["cid"])
]
)
data class UserJoinCourse(
@PrimaryKey(autoGenerate = true) var id: Long? = null,
@ColumnInfo(name = "uid") var uid: Long,
@ColumnInfo(name = "cid") var cid: Long
)
复制代码
UserJoinCourseDao的实现以下:
@Dao
interface UserJoinCourseDao {
@Query("""
select * from tab_user
inner join tab_user_join_course on tab_user.uid = tab_user_join_course.uid
where tab_user_join_course.cid = :cid
""")
fun getUsersByCourseId(cid: Long): List<User>
@Query("""
select * from tab_course
inner join tab_user_join_course on tab_course.cid = tab_user_join_course.cid
where tab_user_join_course.uid = :uid
""")
fun getCoursesByUserId(uid: Long): List<Course>
@Insert
fun insert(vararg userJoinCourses: UserJoinCourse)
}
复制代码
@Relation注解用在查询表的数据时,自动查询关联的其它数据
@Relation注解 不能用于@Entity注解的实体类中
@Relation注解 只能用于一对多(返回值必须是一个集合)
好比:先定义一个CourseWithTeacher类
class CourseWithTeacher (
@Embedded var course: Course,
@Relation(
// entity 标示关联查询的表(非必须),默认匹配返回类型的表
entity = Teacher::class,
// parentColumn 表示 Course 表中的字段(能够是Course表中的任意字段)
// entityColumn 表示 Teacher表的 用于 查询的字段(能够是Teacher表中的任意字段)
// 最后的子查询语句是 (example:SELECT `tid`,`name`,`cid` FROM `tab_teacher` where :entityColumn in [:parentColumn])
parentColumn = "cid",
entityColumn = "cid")
var teachers: List<Teacher>
)
复制代码
而后修改 CourseDao的实现
@Dao
abstract class CourseDao {
@Query("select * from tab_course")
abstract fun getAll(): List<CourseWithTeacher>
}
复制代码
当两张表有外键关联关系的时候,好比tab_user
和 tab_id_card
表用ForeignKey
实现了一对一的关联关系,当删除tab_user
表的一条数据时,若是这条数据被tab_id_card
关联,则删除失败,会报 android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed
错误;这时能够给ForeignKey
添加onDelete属性配置及联删除策略
ForeignKey.NO_ACTION
: 默认策略,不会作任何处理,发现删除的数据被关联,则直接报错
ForeignKey.RESTRICT
: 同NO_ACTION
效果同样, 但它会先检查约束
ForeignKey.SET_NULL
:tab_user
表删除一条数据时,则将对应的tab_id_card
表的uid
的值设置成NULL
ForeignKey.SET_DEFAULT
:tab_user
表删除一条数据时,则将对应的tab_id_card
表的uid
的值设置成默认值,可是因为room暂时没办法给column设置默认值,因此仍是会设置成 NULL
ForeignKey.CASCADE
:tab_user
表删除一条数据时,则同时也会删除tab_id_card
表的所对应的数据
@Entity(
tableName = "tab_id_card",
foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = ["uid"],
childColumns = ["uid"],
onDelete = ForeignKey.CASCADE
)],
indices = [
Index("_uuid", unique = true),
Index("uid", unique = true) // 标示惟一约束,则与tab_user是一对一的关系
]
)
class IDCard(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "_uuid")
var uuid: String,
var startTime: String,
var expireTime: String,
@ColumnInfo(name = "uid")
var userId: Long? = null
)
复制代码
及联更新策略 同 删除策略一致,只是经过onUpdate配置
好比在app的版本迭代过程当中version(版本号)
经历了一、二、三、4
的变动
使用Migration
配置每一个版本的更新规则,其构造函数必须指定startVersion
和 endVersion
代码实现以下:
private fun createAppDataBase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, "db_example")
.addMigrations(object : Migration(1, 2) { // 从1升级到2的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 1-2===")
// do something
}
}).addMigrations(object : Migration(2, 3) {// 从2升级到3的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 2-3===")
// do something
}
})
.addMigrations(object : Migration(3, 4) {// 从3升级到4的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 3-4===")
// do something
}
})
.addMigrations(object : Migration(1, 3) {// 从1升级到3的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 1-3===")
// do something
}
}).addMigrations(object : Migration(2, 4) {// 从2升级到4的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 2-4===")
// do something
}
})
.build()
}
复制代码
当前app数据库version | 最新的app数据库version | 升级规则 | 打印结果 |
---|---|---|---|
1 | 4 | 先从1升级到3,再从3升级到4 | ===Migration 1-3=== ===Migration 3-4=== |
2 | 4 | 直接从2升级到4 | ===Migration 2-4=== |
3 | 4 | 从3升级到4 | ===Migration 3-4=== |
4 | 4 | 不变 |
总结规则以下(以当前version == 1,最新version == 4 为例 ):
version
做为 startVersion
, 匹配最大的endVersion
(即:先从1升级到3)endVersion
最为startVersion
,又匹配最大的endVersion
(即:再从3升级到4)默认状况下,若是没有匹配到升级策略,则app 直接 crash
为了防止crash,可添加fallbackToDestructiveMigration
方法配置 直接删除全部的表,从新建立表
private fun createAppDataBase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, "db_example")
.addMigrations(object : Migration(1, 2) { // 从1升级到2的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 1-2===")
// do something
}
...
// 若是没有匹配到Migration,则直接删除全部的表,从新建立表
.fallbackToDestructiveMigration()
.build()
}
复制代码
例如当前version
是 1或2, 升级到 4 很是麻烦,工做量太大,还不如直接删库重建,这个时候就能够调用fallbackToDestructiveMigrationFrom
方法指定当前version
是多少的时候删表重建
private fun createAppDataBase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, "db_example")
.addMigrations(object : Migration(3, 4) {// 从1升级到3的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 3-4===")
// do something
}
})
// 若是没有匹配到Migration,则直接删除全部的表,从新建立表
.fallbackToDestructiveMigration()
// 须要配合fallbackToDestructiveMigration方法使用,指定当前`version` 是 1或2,则直接删除全部的表,从新建立表
.fallbackToDestructiveMigrationFrom(1, 2)
.build()
}
复制代码
好比在app的版本迭代过程当中version(版本号)
经历了一、二、三、4
的变动,当前是4,须要降级到1
private fun createAppDataBase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, "db_example")
.addMigrations(object : Migration(4, 3) { // 从4降级到3的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 4-3===")
// do something
}
}).addMigrations(object : Migration(3, 2) {// 从3降级到2的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 3-2===")
// do something
}
})
.addMigrations(object : Migration(2, 1) {// 从2降级到1的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 2-1===")
// do something
}
})
.addMigrations(object : Migration(4, 2) {// 从4降级到2的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 4-2===")
// do something
}
}).addMigrations(object : Migration(3, 1) {// 从3降级到1的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 3-1===")
// do something
}
})
.build()
}
复制代码
当前app数据库version | 降级到app数据库version | 升级规则 | 打印结果 |
---|---|---|---|
4 | 1 | 先从4降级到2,再从2降级到1 | ===Migration 4-2=== ===Migration 2-1=== |
4 | 2 | 直接从4降级到2 | ===Migration 4-2=== |
4 | 3 | 从4降级到3 | ===Migration 4-3=== |
4 | 4 | 不变 |
总结规则以下(以当前version == 4,降级到version == 1 为例 ):
version
做为 startVersion
, 匹配最小的endVersion
(即:先从4降级到2)endVersion
最为startVersion
,又匹配最小的endVersion
(即:再从2降级到1)private fun createAppDataBase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, "db_example")
.addMigrations(object : Migration(4, 3) { // 从4降级到3的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 4-3===")
// do something
}
})
// 若是没有匹配到降级Migration,则删表重建
.fallbackToDestructiveMigrationOnDowngrade()
.build()
}
复制代码
从1 升级到 2,添加一张表tab_test表
private fun createAppDataBase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, "db_example")
.addMigrations(object : Migration(1, 2) { // 从1升级到2的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 1-2===")
database.execSQL("""
CREATE TABLE IF NOT EXISTS `tab_test` (
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
`username` TEXT,
`age` INTEGER NOT NULL
)
""".trimIndent())
}
})
.fallbackToDestructiveMigration()
.fallbackToDestructiveMigrationOnDowngrade()
.build()
}
复制代码
从2 升级到 3,给tab_test表添加 desc 字段
private fun createAppDataBase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, "db_example")
.addMigrations(object : Migration(1, 2) { // 从1升级到2的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 1-2===")
database.execSQL("""
CREATE TABLE IF NOT EXISTS `tab_test` (
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
`username` TEXT,
`age` INTEGER NOT NULL
)
""".trimIndent())
}
})
.addMigrations(object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 2-3===")
database.execSQL("ALTER TABLE `tab_test` ADD COLUMN `desc` TEXT")
}
})
.fallbackToDestructiveMigration()
.fallbackToDestructiveMigrationOnDowngrade()
.build()
}
复制代码
从3 升级到 4,给tab_test表 desc 字段 重命名为 desc2
private fun createAppDataBase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, "db_example")
.addMigrations(object : Migration(1, 2) { // 从1升级到2的实现
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 1-2===")
database.execSQL("""
CREATE TABLE IF NOT EXISTS `tab_test` (
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
`username` TEXT,
`age` INTEGER NOT NULL
)
""".trimIndent())
}
})
.addMigrations(object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 2-3===")
database.execSQL("ALTER TABLE `tab_test` ADD COLUMN `desc` TEXT")
}
})
.addMigrations(object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
Log.i("AppDataBase", "===Migration 3-4===")
// 重命名tmp_tab_test
database.execSQL("ALTER TABLE `tab_test` RENAME TO `tmp_tab_test`")
// 从新建立表tab_test
database.execSQL("""
CREATE TABLE IF NOT EXISTS `tab_test` (
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
`username` TEXT,
`age` INTEGER NOT NULL,
`desc2` TEXT
)
""".trimIndent())
// 将表tmp_tab_test的数据复制到tab_test
database.execSQL("insert into `tab_test` select * from `tmp_tab_test`")
// 删除tmp_tab_test表
database.execSQL("drop table `tmp_tab_test`")
}
})
.fallbackToDestructiveMigration()
.fallbackToDestructiveMigrationOnDowngrade()
.build()
}
复制代码
implementation "androidx.room:room-runtime:2.1.0"
kapt "androidx.room:room-compiler:2.1.0" // For Kotlin use kapt instead of annotationProcessor
复制代码
facebook
的 stetho library
配合调试你的数据库###快速查询tab_user表的建表语句(方便升级时建表使用)
SELECT sql FROM sqlite_master WHERE name='tab_user';
复制代码