testImplementation “androidx.room:room-testing:$room_version”
}
[]( )主要组件
===============================================================
* `数据库`:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用 `@Database`注释的类应满足以下条件:
* 是扩展 `RoomDatabase` 的抽象类。
* 在注释中添加与数据库关联的实体列表。
* 包含具有 0 个参数且返回使用`@Dao`注释的类的抽象方法。
在运行时,您可以通过调用 `Room.databaseBuilder()` 或 `Room.inMemoryDatabaseBuilder()`获取 `Database`的实例。
* `Entity`:表示数据库中的表。
* `DAO`:包含用于访问数据库的方法。
应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。 最后,应用使用实体来获取和设置与数据库中的表列相对应的值。
关系如图:

ok,基本概念了解之后,看一下具体是怎么搞的。
### []( )Entity
@Entity(tableName = “t_history”)
data class History(
/**
* @PrimaryKey主键,autoGenerate = true 自增
* @ColumnInfo 列 ,typeAffinity 字段类型
* @Ignore 忽略
*/
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
val id: Int? = null,
@ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
val name: String?,
@ColumnInfo(name = "insert_time", typeAffinity = ColumnInfo.TEXT)
val insertTime: String?,
@ColumnInfo(name = "type", typeAffinity = ColumnInfo.INTEGER)
val type: Int = 1
)
* Entity对象对应一张表,使用`@Entity`注解,并声明你的表名即可
* `@PrimaryKey` 主键,`autoGenerate = true` 自增
* `@ColumnInfo` 列,并声明列名 ,`typeAffinity` 字段类型
* `@Ignore` 声明忽略的对象
很简单的一张表,主要是`name`和`insertTime`字段。
### []( )DAO
@Dao
interface HistoryDao {
//按类型 查询所有搜索历史
@Query("SELECT * FROM t_history WHERE type=:type")
fun getAll(type: Int = 1): Flow<List<History>>
@ExperimentalCoroutinesApi
fun getAllDistinctUntilChanged() = getAll().distinctUntilChanged()
//添加一条搜索历史
@Insert
fun insert(history: History)
//删除一条搜索历史
@Delete
fun delete(history: History)
//更新一条搜索历史
@Update
fun update(history: History)
//根据id 删除一条搜索历史
@Query("DELETE FROM t_history WHERE id = :id")
fun deleteByID(id: Int)
//删除所有搜索历史
@Query("DELETE FROM t_history")
fun deleteAll()
}
* @Insert:增
* @Delete:删
* @Update:改
* @Query:查
这里有一个点需要注意的,就是`查询所有搜索历史`返回的集合我用`Flow`修饰了。
只要是数据库中的任意一个数据有更新,无论是哪一行数据的更改,那就重新执行 `query`操作并再次派发`Flow`。
同样道理,如果一个不相关的数据更新时,`Flow`也会被派发,会收到与之前相同的数据。
> 这是因为 SQLite 数据库的内容更新通知功能是以表 (Table) 数据为单位,而不是以行 (Row) 数据为单位,因此只要是表中的数据有更新,它就触发内容更新通知。Room 不知道表中有更新的数据是哪一个,因此它会重新触发 DAO 中定义的 query 操作。您可以使用 Flow 的操作符,比如 distinctUntilChanged 来确保只有在当您关心的数据有更新时才会收到通知。
//按类型 查询所有搜索历史
@Query("SELECT * FROM t_history WHERE type=:type")
fun getAll(type: Int = 1): Flow<List<History>>
@ExperimentalCoroutinesApi
fun getAllDistinctUntilChanged() = getAll().distinctUntilChanged()
### []( )数据库
@Database(entities = [History::class], version = 1)
abstract class HistoryDatabase : RoomDatabase() {
abstract fun historyDao(): HistoryDao
companion object {
private const val DATABASE_NAME = "history.db"
private lateinit var mPersonDatabase: HistoryDatabase
//注意:如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循单例设计模式。
//每个 RoomDatabase 实例的成本相当高,而您几乎不需要在单个进程中访问多个实例
fun getInstance(context: Context): HistoryDatabase {
if (!this::mPersonDatabase.isInitialized) {
//创建的数据库的实例
mPersonDatabase = Room.databaseBuilder(
context.applicationContext,
HistoryDatabase::class.java,
DATABASE_NAME
).build()
}
return mPersonDatabase
}
}
}
* 使用`@Database`注解声明
* `entities` 数组,对应此数据库中的所有表
* `version` 数据库版本号
**注意:**
> 如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循`单例设计模式`。 每个 RoomDatabase
> 实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。
[]( )使用
=============================================================
在需要的地方获取数据库
mHistoryDao = HistoryDatabase.getInstance(this).historyDao()
### []( )获取搜索历史
private fun getSearchHistory() {
MainScope().launch(Dispatchers.IO) {
mHistoryDao.getAll().collect {
withContext(Dispatchers.Main){
//更新ui
}
}
}
}
`collect` 是`Flow`获取数据的方式,并不是唯一方式,可以[查看文档]( )。
为什么放在`协程`里面呢,因为数据库的操作是费时的,而协程可以轻松的指定线程,这样不阻塞`UI`线程。
查看Flow源码也发现,Flow是协程包下的
package kotlinx.coroutines.flow
以collect为例,也是被`suspend` 修饰的,既然支持`挂起`,那配合`协程`岂不美哉。
@InternalCoroutinesApi
public suspend fun collect(collector: FlowCollector<T>)
### []( )保存搜索记录
private fun saveSearchHistory(text: String) {
MainScope().launch(Dispatchers.IO) {
mHistoryDao.insert(History(null, text, DateUtils.longToString(System.currentTimeMillis())))
}
}
### []( )清空本地历史
private fun cleanHistory() {
MainScope().launch(Dispatchers.IO) {
mHistoryDao.deleteAll()
}
}
> 作者:[https://blog.youkuaiyun.com/yechaoa]( )
[]( )数据库升级
================================================================
数据库升级是一个重要的操作,毕竟可能会造成数据丢失,也是很严重的问题。
Room通过`Migration`类来执行升级的操作,我们只要告诉`Migration`类改了什么就行,比如`新增`字段或表。
### []( )定义Migration类
/**
* 数据库版本 1->2 t_history表格新增了updateTime列
*/
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE t_history ADD COLUMN updateTime String")
}
}
/**
* 数据库版本 2->3 新增label表
*/
private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `t_label` (`id` INTEGER PRIMARY KEY autoincrement, `name` TEXT)")
}
}
`Migration`接收两个参数:
* startVersion 旧版本
* endVersion 新版本
### []( )通知数据库更新
mPersonDatabase = Room.databaseBuilder(
context.applicationContext,
HistoryDatabase::class.java,
DATABASE_NAME
).addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()