1. Room 的优势
Room是JetPack的一种关系型数据库,Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
- 针对 SQL 查询的编译时验证。
- 可最大限度减少重复和容易出错的样板代码的方便注解。
- 简化了数据库迁移路径。
2. 主要组件
Room 包含三个主要组件:
- Entity:实体类,对应的是数据库的一张表结构,使用注解@Entity标记
- Dao:包含访问一系列访问数据库的方法,使用注解@Dao标记
- DataBase:数据库持有者,作为与应用持久化相关数据的底层连接的主要接入点。使用注解@Database标记,另外需要满足以下条件:定义的类必须继承于RoomDatabase的抽象类,在注解中需要定义与数据库相关联的实体类列表。包含一个没有参数的抽象方法并且返回一个Dao对象。
3. 简单使用介绍
3.1 导包
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "androidx.room:room-runtime:2.4.0-alpha04"
kapt "androidx.room:room-compiler:2.4.0-alpha04"
testImplementation "androidx.room:room-testing:2.4.0-alpha04"
implementation "androidx.room:room-ktx:2.2.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "com.blankj:utilcodex:1.29.0"
implementation "com.google.code.gson:gson:2.8.7"
3.2 User实体类,UserDao接口,UserDataBase
User实体类定义:
@Entity(tableName = "user")
class User {
@Ignore
constructor(id: Int) {
this.id = id
}
@Ignore
constructor(firstName: String?, age: Int) {
this.firstName = firstName
this.age = age
}
constructor(id: Int, firstName: String?, age: Int) {
this.id = id
this.firstName = firstName
this.age = age
}
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
var id: Int = 0
@ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)
var firstName: String? = null
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
var age: Int = 0
}
UserDao操作接口类
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
@Delete
fun deleteUser(user: User)
@Update
fun updateUser(user: User)
@Query("SELECT * FROM user")
suspend fun getAllUser(): List<User>
@Query("SELECT * FROM user")
fun getAllUserFlow(): Flow<List<User>>
@Query("SELECT * FROM user")
fun getAllUserLiveData(): LiveData<List<User>?>
}
UserDataBase类
@Database(
entities = [User::class],
version = 1,
exportSchema = false
)
abstract class UserDataBase : RoomDatabase() {
companion object {
private var INSTANCE: UserDataBase? = null
fun init(context: Context) {
if (INSTANCE == null) {
synchronized(UserDataBase::class.java) {
if (INSTANCE == null) {
create(context)
}
}
}
}
private fun create(context: Context) {
INSTANCE =
Room.databaseBuilder(context, UserDataBase::class.java, "user_db")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
}
fun getDB(): UserDataBase {
return INSTANCE ?: throw NullPointerException("UserDataBase 未初始化")
}
}
abstract fun getUserDao(): UserDao
}
增删改查操作:
UserDataBase.init(this)
val user1 = User("小明", 12)
val user2 = User("小红", 18)
val user3 = User("小清", 20)
UserDataBase.getDB().getUserDao().insertUsers(user1, user2, user3)
val user = User(3)
UserDataBase.getDB().getUserDao().deleteUser(user)
val user = User(1, "大明明3333", 15)
UserDataBase.getDB().getUserDao().updateUser(user)
flow {
emit(UserDataBase.getDB().getUserDao().getAllUser())
}.flowOn(Dispatchers.IO)
.onEach {
mAdapter.updateData(it.toMutableList())
}.launchIn(lifecycleScope)
可以看到,操作非常简单,代码简洁清晰。
4. 数据库的升级
比如上面的User表,想在表中增加一行city,String类型怎么办?
4.1 直接升级
修改User的属性
@Entity(tableName = "user")
class User {
@Ignore
constructor(id: Int) {
this.id = id
}
@Ignore
constructor(firstName: String?, age: Int) {
this.firstName = firstName
this.age = age
}
constructor(id: Int, firstName: String?, age: Int) {
this.id = id
this.firstName = firstName
this.age = age
}
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
var id: Int = 0
@ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)
var firstName: String? = null
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
var age: Int = 0
var city: String? = null
}
将UserDataBase的version修改为2,
@Database(
entities = [User::class],
version = 2,
exportSchema = false
)
abstract class UserDataBase : RoomDatabase() {...
Room.databaseBuilder(context, UserDataBase::class.java, "user_db"
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
这种方式好处是暴力快速,坏处是会清除以前数据库中的数据,不建议用这种方式。
4.2 Migration方式,手动迁移
private val Migration_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE user ADD COLUMN city TEXT")
}
}
INSTANCE = Room.databaseBuilder(context, UserDataBase::class.java, "user_db")
.addMigrations(Migration_1_2)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
这种方式好处是会保留以前的数据,不好的地方是要自己写sql语句,有些sql语句比较复杂,而且很容易写错。那有没有更好的方法呢?
4.3 autoMigrations方法,自动迁移
注意:Room 在 2.4.0-alpha01 及更高版本中支持自动迁移。如果您的应用使用的是较低版本的 Room,则必须[手动定义迁移]。
@Database(
version = 2,
entities = [User::class],
autoMigrations = [
AutoMigration (from = 1, to = 2)
]
)
abstract class UserDataBase : RoomDatabase() {
...
}
注意:Room 自动迁移依赖于为旧版和新版数据库生成的数据库架构。如果exportSchema设为false ,或者如果您尚未使用新版本号编译数据库,自动迁移将会失败。
5. exportSchema数据库升级测试记录
@Database(
entities = [User::class],
version = 5,
exportSchema = true,
autoMigrations = [ AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3),
AutoMigration(from = 3, to = 4),
AutoMigration(from = 4, to = 5)]
)
abstract class UserDataBase : RoomDatabase() {
...
}
build.gradle中的defaultConfig设置
defaultConfig {
applicationId "com.hjq.room"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments += ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
运行程序后,会在项目目录下多一个schemas文件夹,
可以记录下来数据库版本升级的修改,方便维护与测试。
例如1.json如下:
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "51f493557826d6594d5cb9ea8939e231",
"entities": [
{
"tableName": "user",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_name` TEXT, `age` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "firstName",
"columnName": "first_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "age",
"columnName": "age",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '51f493557826d6594d5cb9ea8939e231')"
]
}
}
6. 监听表数据变化
6.1 LiveData方式
@Query("SELECT * FROM user")
fun getAllUserLiveData(): LiveData<List<User>?>
mViewModel.getUserLiveData().observe(this) { t ->
LogUtils.d("data change")
if (!t.isNullOrEmpty()) {
mAdapter.updateData(t.toMutableList()) }
}
6.2 Flow流的方式
@Query("SELECT * FROM user")
fun getAllUserFlow(): Flow<List<User>>
mViewModel.getUserFlow().onEach {
LogUtils.d("data change")
mAdapter.updateData(it.toMutableList())
}.launchIn(lifecycleScope)
6.3 Rxjava的方式
感兴趣的同学可以查看官网。
7. 引用复杂数据
比如在上面的User中要引入一个UserPosition数据
class UserPosition {
var latitude: Double = 0.0
var longitude: Double = 0.0
var altitude: Double = 0.0
}
@ColumnInfo(name = "user_position")
var userPosition: UserPosition? = null
object UserPositionConverter {
@TypeConverter
fun objectToString(value: String?): UserPosition? {
try {
return Gson().fromJson(value, UserPosition::class.java)
} catch (e: Exception) {
LogUtils.d("e===$e")
e.printStackTrace()
}
return null
}
@TypeConverter
fun stringToObject(deviceEventsBean: UserPosition?): String {
try {
return Gson().toJson(deviceEventsBean)
} catch (e: Exception) {
LogUtils.d("e===$e")
e.printStackTrace()
}
return ""
}
}
@Entity(tableName = "user")
@TypeConverters(UserPositionConverter::class)
class User {
@Ignore
constructor(id: Int) {
this.id = id
}
@Ignore
constructor(firstName: String?, age: Int) {
this.firstName = firstName
this.age = age
}
constructor(id: Int, firstName: String?, age: Int) {
this.id = id
this.firstName = firstName
this.age = age
}
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
var id: Int = 0
@ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)
var firstName: String? = null
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
var age: Int = 0
var city: String? = null
@ColumnInfo(name = "user_position")
var userPosition: UserPosition? = null
}
8. 预填充 Room 数据库
有时,您可能希望应用启动时数据库中就已经加载了一组特定的数据。这称为预填充数据库。 在 Room 2.2.0 及更高版本中,您可以使用 API 方法在初始化时用设备文件系统中预封装的数据库文件中的内容预填充 Room 数据库。
注意:[内存中 Room 数据库]不支持使用 [createFromAsset() ] 或 [createFromFile() ]预填充数据库。
8.1 从应用资源中填充
如需从位于应用 assets/ 目录中的任意位置的预封装数据库文件预填充 Room 数据库,请先从 RoomDatabase.Builder 对象调用 createFromAsset() 方法,然后再调用 build() :
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.build()
8.2 从文件系统预填充
如需从位于设备文件系统任意位置(应用的 assets/ 目录除外)的预封装数据库文件预填充 Room 数据库,请先从 RoomDatabase.Builder 对象调用 createFromFile() 方法,然后再调用 build() :
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromFile(File("mypath"))
.build()
示例代码
代码
|