DAO
@Dao
interface HistoryDao {
//按类型 查询所有搜索历史
@Query(“SELECT * FROM t_history WHERE type=:type”)
fun getAll(type: Int = 1): Flow<List>
@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>
@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)
保存搜索记录
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()
}
}
================================================================
数据库升级是一个重要的操作,毕竟可能会造成数据丢失,也是很严重的问题。
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()
完整代码
@Database(entities = [History::class, Label::class], version = 3)
abstract class HistoryDatabase : RoomDatabase() {
abstract fun historyDao(): HistoryDao
companion object {
private const val DATABASE_NAME = “history.db”
private lateinit var mPersonDatabase: HistoryDatabase
fun getInstance(context: Context): HistoryDatabase {
if (!this::mPersonDatabase.isInitialized) {
//创建的数据库的实例
mPersonDatabase = Room.databaseBuilder(
context.applicationContext,
HistoryDatabase::class.java,
DATABASE_NAME
).addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
}
return mPersonDatabase
}
/**
- 数据库版本 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)”)
}
}
}
}
注意:
@Database
注解中版本号的更改,如果是新增表的话,entities
参数里也要添加上。
建议升级操作顺序
修改版本号 -> 添加Migration -> 添加给databaseBuilder
==================================================================
Room 具有以下注解处理器选项:
-
room.schemaLocation
:配置并启用将数据库架构导出到给定目录中的 JSON 文件的功能。如需了解详情,请参阅 Room 迁移。 -
room.incremental
:启用 Gradle 增量注释处理器。 -
room.expandProjection
:配置 Room 以重写查询,使其顶部星形投影在展开后仅包含 DAO 方法返回类型中定义的列。
android {
…
defaultConfig {
…
javaCompileOptions {
annotationProcessorOptions {
arguments += [
“room.schemaLocation”:“$projectDir/schemas”.toString(),
“room.incremental”:“true”,
“room.expandProjection”:“true”]
}
}
}
}
配置好之后,编译运行,module文件夹下会生成一个schemas
文件夹,其下有一个json
文件,里面包含数据库的基本信息。
{
“formatVersion”: 1,
“database”: {
“version”: 1,
总结
最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
数据库的基本信息。
{
“formatVersion”: 1,
“database”: {
“version”: 1,
总结
最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
[外链图片转存中…(img-HXeXnAfM-1715062016343)]
[外链图片转存中…(img-7OxjOtLR-1715062016345)]
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!