android Room的使用
老规矩,在做完一个项目之后需要对开发过程中使用到的新知识点做一个总结,最近使用kt完成了一个纯上层的app, 其中对数据库的操作使用Room框架来完成,大大节省了工作量,可以说用的很爽,因此本节就这个知识点来进行一系列的总结,以便填充本人对整个Jetpack库知识的积累。
- 概念
- 集成
- 应用
- 特性
概念
Room是jetpack开发包中的一个基于SQLite开发的一个数据库操作抽象层框架,官方推出的这个框架能够针对sql命令在工程编译时进行校验,且简化了数据库的迁移工作,最大限度的减少了数据库操作的样板代码等。
集成
工程的gradle文件(最外层的build.gradle)中添加依赖:
dependencies {
......
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
}
模块的gradle文件(app中的build.gradle或者其他module中的gradle)配置如下:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'kotlin-android-extensions'
}
android {
//如果不需要导出数据库的schema文件则无需配置此项
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
}
dependencies {
kapt "androidx.room:room-compiler:2.4.2"
implementation "androidx.room:room-runtime:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
}
应用
app中应用Room主要分为三个模块:数据库类,数据操作类,数据实体类。按照依赖顺序介绍,先从数据实体类开始介绍
数据实体类
- @Entity
类注解, @Entity注解可以定义表的名称,如果没有自定义,默认数据表的名称为实体类的类名。
- @PrimaryKey
属性注解,用来定义数据表的主键
- @ColumnInfo
属性注解,用来定义数据表中各个列的信息,如列名,存储的数据格式等
- @Ignore
属性注解,用来修饰此属性不被记录到数据表中(不在数据库中创建属性对应的列)
举例如下,创建了student_table与ClassN两个表
//创建数据表为stub_table
@Entity(tableName = "student_table")
data class Student(@PrimaryKey val id:Int,
@ColumnInfo(name="custom_name", typeAffinity = ColumnInfo.TEXT) val name:String,
@ColumnInfo val age:Int,
@ColumnInfo val classId:Int)
@Entity
data class ClassN(@PrimaryKey val classId:Int,
@ColumnInfo val className:String)
数据操作类(Dao)
Dao 必须是接口或者抽象类,因为 Room 在编译期间生成他们的实现类.
- @Dao
类注解, Dao注解用来修饰数据库操作的Dao类
- @Insert
方法注解, 用来定义该方法用于数据库数据插入操作
- @Update
方法注解, 用来定义该方法用于数据库数据更新操作
- @Delete
方法注解, 用来定义该方法用于数据库数据删除操作
- @Query
方法注解, 查询具体的数据,或者执行自定义的SQLite命令, insert,update,
delete等操作均可使用@Query注解然后自定义sql命令完成
相关实现如下:
@Dao
interface SchoolDao {
//插入N条ClassN数据
@Insert
fun insertClass(vararg classN: ClassN)
//查询数据表中的所有行,并将其转为ClassN的集合
@Query("SELECT * FROM ClassN")
fun queryClass() : List<ClassN>
//查询所有classId在ids数组中的行
@Query("SELECT * FROM user WHERE classId IN (:ids)")
fun queryClass(ids: IntArray) : List<ClassN>
//删除对应的ClassN对象
@Delete
fun deleteClass(cn: ClassN)
//删除指定classId = cid的行
@Query("DELETE FROM ClassN where classId = :classId")
fun deleteClass(cid : Int)
//更新表中的ClassN
@Update
fun updateClass(cn:ClassN)
}
数据库创建类
数据库访问与实体都创建好了之后,我们需要创建一个数据库,然后通过该类对外提供各类Dao的获取接口, 实现步骤如下:
- 定义抽象类继承自RoomDatabase
- 使用@DataBase注解类
@DataBase注解中有挺多的值,其中基本的指定数据库的版本[version=数据库版本];指定各类数据实体 [ entities = [Student::class, ClassN::class]]
- 定义各种Dao类的获取方法
abstract fun schoolDao() : SchoolDao
abstract fun studentDao() : StudentDao
- 使用Room类的databaseBuilder来构造数据库
Room.databaseBuilder(ctx, AppDatabase::class.java, "test_db")
.addCallback(object : RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.i("debug", "----------> db create")
}
})
.build()
完整代码如下:
@Database(version=1, entities = [Student::class, ClassN::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun schoolDao() : SchoolDao
abstract fun studentDao() : StudentDao
companion object {
@Volatile
private var instance : AppDatabase ?= null
fun instance(ctx:Context) : AppDatabase{
return instance ?: synchronized(this){
instance ?: buildDataBase(ctx).also {
instance = it
}
}
}
private fun buildDataBase(ctx:Context) : AppDatabase{
return Room.databaseBuilder(ctx, AppDatabase::class.java, "test_db")
.addCallback(object : RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.i("debug", "----------> db create")
}
})
.build()
}
}
}
其他特性
Android Room可以直接返回数据的数据类型以外,还可以直接以异步流(Flow)的方式返回,如下:
@Query("select * from ClassN")
fun queryClassByFlow() : Flow<List<ClassN>>
CoroutineScope(Dispatchers.Main).launch {
AppDatabase.instance(baseContext)
.schoolDao()
.queryClassByFlow()
.map {
var str = ""
it.forEach {
str = "${str}-${it.classId}-${it.className}"
}
str
}
.flowOn(Dispatchers.IO)
.collectLatest { text->
content.text = text
}
}
通过结合异步流,我们可以动态监听数据表中的某个数据变化;使用Flow的distinctUntilChanged()方法可以让我们在数据变化后得到通知,如下:
Dao中的定义
@Query("select * from ClassN where classId=:classId")
fun queryClassByFlow(classId:Int) : Flow<ClassN>
fun queryClassDistinct(classId : Int) = queryClassByFlow(classId).distinctUntilChanged()
界面中监听数据
CoroutineScope(Dispatchers.Main).launch {
AppDatabase.instance(baseContext).schoolDao().queryClassDistinct(2)
.flowOn(Dispatchers.IO)
.collectLatest {
content.text = it.className
}
}
这样每次classId为2的数据变化后,我们在collectLatest中都能收到通知