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';
複製代碼