Jetpack Room是Android官方供给的一个持久化库,旨在简化Android应用程序中的数据库操作。它供给了一个笼统层,使开发人员能够以面向目标的方式处理数据库操作,而无需编写复杂的SQL查询句子。经过运用Jetpack Room,开发人员能够更快速、更高效地构建稳健的数据库驱动应用程序。
Room 最新版别为2.5.2
Android Studio版别为 Android Studio Flamingo | 2022.2.1 Patch 2
Android运用Jetpack Room办理数据库 – 第一弹
Android运用Jetpack Room办理数据库 – 第二弹
装备Room
在运用Room之前,咱们需求增加它的依靠进入到工程,首先在app模块的build.gradle
中增加依靠项
dependencies {
...
def room_version = "2.5.2"
implementation "androidx.room:room-runtime:$room_version"
// kotlin需求kapt,java则是annotationProcessor
kapt "androidx.room:room-compiler:$room_version"
}
因为Room依靠需求kapt
插件,所以咱们还需求增加kapt
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
同步下工程之后,咱们就能够在工程中运用Room来办理数据库了。
简单操作Room
在介绍初始化之前,咱们先了解下几个相关的概念
-
Entity
用来界说数据库中的某个表和表中的数据结构; -
Dao
用来办理和操作数据库中的表,包括常见的增、删、改、查等操作; -
Database
承继自RoomDatabase
类,它是笼统类,AS会自动为咱们生成它的完成,用于办理数据库的称号、版别和晋级等操作,而且能够从它获取Dao
的完成类。
了解了上面的概念之后,下面咱们直接进入运用Room的环节,一起来看看Room是如何帮咱们简化数据库的操作。
第一步先界说一个实体类,用于表明数据库中某个表的结构
@Entity(tableName = "user_entity")
data class UserEntity(
val name: String,
@PrimaryKey
val id: Int
)
咱们界说了一个UserEntity
数据类,类的注解选用@Entity
润饰,表明它是数据库中的一张表,设置了表名为user_entity
,而且给表的主键设置为id
字段,这个主键能够协助咱们在刺进相同id
时给予抵触战略(后面会具体介绍)。
第二步界说咱们user_entity
的Dao
类,将增修正查办法先界说好
@Dao
interface UserDao {
// 设置主键抵触之后的战略,这儿选择直接掩盖原数据
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(userEntity: UserEntity)
// 删去某条数据
@Delete
fun deleteUser(userEntity: UserEntity)
// 更新某条数据
@Update
fun updateUser(userEntity: UserEntity)
// 依据id查找数据
@Query("select * from user_entity where id=:id")
fun findUser(id: Int): List<UserEntity>
}
这儿咱们界说了一个UserDao
接口,留意是接口类哦,而且运用@Dao
注解进行润饰,内部界说了四个办法,分别为增修正查操作。留意看insert
办法的注解,其间onConflict
参数便是用来处理主键抵触的,当咱们刺进一个数据时,表中现已有此主键数据,这时分Room就会依据咱们设置的战略来处理这条新增的数据:
-
OnConflictStrategy.REPLACE
假如发生抵触,直接掩盖已有数据,将表中现存的数据替换成刺进的这条; -
OnConflictStrategy.IGNORE
假如发生抵触,直接疏忽此次刺进操作 -
OnConflictStrategy.NONE
这个是默认的战略,它和ABORT
效果是共同的,都是停止此次刺进操作,而且抛出SQLiteConstraintException
-
OnConflictStrategy.ROLLBACK
这个表明假如发生抵触,停止刺进操作,而且将事务回滚到开始的状态,在最新版别现已被符号@Deprecated
引荐运用ABORT
-
OnConflictStrategy.ABORT
和NONE
效果共同,这儿就不过多介绍 -
OnConflictStrategy.FAIL
这个表明假如发生抵触,停止刺进操作,而且抛出SQLiteConstraintException
异常,在最新版别也是被符号@Deprecated
,也是引荐运用ABORT
。
以上便是在刺进操作过程中,主键抵触时,一切的战略方式,大家能够按需求选用。
界说好Dao
之后,咱们就能够装备RoomDatabase
了,少了它咱们还不能运用Room来操作数据库呢。
@Database(entities = [UserEntity::class], version = 1, exportSchema = false)
abstract class RoomDb : RoomDatabase() {
abstract fun getUserDao(): UserDao
}
界说一个RoomDatabase
子类,而且它是笼统办法,选用@Database
注解润饰,注解中带了三个参数:
-
entities
表明一切的实体类,也是就Room要创立的表结构,它是一个数组类型,能够创立多个表; -
version
表明的是数据库的版别,这个在后面的数据库晋级中会具体介绍,重要信息之一; -
exportSchema
这个参数只是代表是否能够在编译的时分导出数据库的装备文件,假如你需求看装备能够设置为true
,默认的装备文件会在app/build/
的schema
文件夹中。
内部有一个笼统办法,是用于获取UserDao
实例,这儿AS会默以为咱们生成完成类,具体生成的类在build/generated/source/kapt/com/...
,生成之后的类名是咱们界说的类名加上_Impl
。
最终咱们需求创立RoomDb
单例目标,这儿咱们选用的是Koin
库协助咱们简化操作,具体Koin
的运用前几篇文章有具体介绍,小伙伴们能够去了解下。
val module = module {
// 创立RoomDb的单例目标
single {
Room.databaseBuilder(androidContext(), RoomDb::class.java, "room_db")
.build()
}
// 创立UserDao的单例目标
single { get<RoomDb>().getUserDao() }
}
在创立数据库RoomDb
目标时,选用的是build
方式,传入了Context
、RoomDb
和数据库称号,这儿仍是比较简单的,在后面涉及数据库晋级时,咱们仍是会回到此处,增加晋级操作。
到这儿数据库的准备工作现已完成了,接下来就能够实践一下最常用的增修正查操作了,趁便提一下,AS现在能够直接查看和调试App的数据库了,在App inspection
工具栏里边就能够体验。
class MainActivity : AppCompatActivity() {
// 获取UserDao单例目标
private val userDao by inject<UserDao>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.tv).setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
// 刺进一条数据
userDao.insertUser(UserEntity(1, "taonce"))
}
}
findViewById<TextView>(R.id.textView).setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
// 将name更新为taonce_update
userDao.updateUser(UserEntity(1, "taonce_update"))
}
}
findViewById<TextView>(R.id.textView2).setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
// 查找表中id为1的数据
val entityList = userDao.findUser(1)
entityList.forEach { Log.d(TAG, "find user: $it") }
}
}
findViewById<TextView>(R.id.textView3).setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
// 删去此UserEntity数据
userDao.deleteUser(UserEntity(1, "taonce_update"))
}
}
}
}
当咱们点击刺进数据后,咱们能够在App Inspection
中实时查看到数据的变化。
假如你想实时的调查表中数据变化,记住勾选Live updates框,未勾选的状况需求手动点击前面的改写图标。App Inspection
还能够直接运用SQL
句子来操作表,你能够在调试过程中修正或许模仿一些数据。
左面红框便是新建SQL
句子的进口。
Entity的高阶用法
上面提到了在数据类选用@Entity
注解之后,表结构就会依据数据类的字段来生成对应表字段,假如有个数据类的字段许多,可是咱们又不想悉数存入数据库,或许这个数据类引进了别的数据类此刻对应的表结构会是怎样呢?
疏忽字段
当咱们不想表中存入悉数字段时,咱们能够选用疏忽某些字段的方式来解决这种问题。
@Entity
data class ArticleEntity(
@PrimaryKey()
var articleId: Long = 0L,
var title: String = "",
var url: String = "",
var author: String = "",
@Ignore
var authorAlisa: String = ""
)
模仿了一个文章实体类,它内部包括许多信息,可是authorAlisa
这个字段咱们并不想存入到表中,这个就能够选用@Ignore
注解来疏忽此字段,最终的表结构经过App Inspection
看下,它是不包括authorAlisa
字段的。
嵌套目标
当咱们界说的数据类中嵌套了别的一个或许多个数据类时,假如不做任何操作Room是无法为咱们创立对应的表结构,咱们需求显示的经过@Embedded
注解告诉Room此字段为嵌入目标,需求将嵌入目标的字段也加入到表中。
@Entity
data class ArticleEntity(
@PrimaryKey()
var articleId: Long = 0L,
var title: String = "",
var url: String = "",
var author: String = "",
@Ignore
var authorAlisa: String = "",
// 嵌入了AuthorDetail目标
@Embedded
var authorDetail: AuthorDetail = AuthorDetail()
)
data class AuthorDetail(
val authorId: Long = 0L,
val authorName: String = "",
val joinTime: String = "",
val updateTime: String = ""
)
上面咱们在ArticleEntity
数据类中嵌入了AuthorDetail
目标,Room会将被嵌入目标的字段也一并加入到表中,仍是经过App Inspection
来调查下表结构。
从上面的图片就能够看出被嵌入目标的字段也一起加入到AuthorEntity
表中了。
嵌入List目标
当咱们界说的实体类中含有List
字段时,而且在不疏忽此字段的状况下,无法经过@Embedded
嵌入目标的方式来引进其内部字段,这时分就需求经过TypeConverter
的方式来操作List
字段了。
首先咱们模仿带有List
字段的实体类,而且在类上经过@TypeConverters
注解指定类型转化
@TypeConverters(AuthorDetailConvert::class)
@Entity
data class ArticleEntity(
@PrimaryKey()
var articleId: Long = 0L,
var title: String = "",
var url: String = "",
var author: String = "",
@Ignore
var authorAlisa: String = "",
var authorDetail: List<AuthorDetail> = listOf()
)
data class AuthorDetail(
val authorId: Long = 0L,
val authorName: String = "",
val joinTime: String = "",
val updateTime: String = ""
)
然后再看看咱们界说的类型转化具体完成
@ProvidedTypeConverter
class AuthorDetailConvert {
@TypeConverter
fun string2AuthorDetailList(detailList: String): List<AuthorDetail> {
return Gson().fromJson(detailList, object : TypeToken<List<AuthorDetail>>() {}.type)
}
@TypeConverter
fun authorDetailList2String(list: List<AuthorDetail>): String {
return Gson().toJson(list)
}
}
此类有必要经过@ProvidedTypeConverter
注解润饰,表明它供给某种具体的类型转化,内部界说两个办法,办法也有必要经过@TypeConverter
注解润饰。
-
authorDetailList2String
办法具体含义便是将List<AuthorDetail>
经过Gson转化成字符串的方式,这个是用来刺进数据时调用的办法; -
string2AuthorDetailList
办法则是相反,它是将字符串经过Gson转化成咱们需求的List<AuthorDetail>
目标,这个是用来从数据库中取出数据时调用的办法。
总得来说也便是咱们在存数据到库中的时分,会将List<T>
目标转化成字符串存入到表中,它在表中是一个字段,然后再取数据时直接将字符串转化成对应List<T>
目标,这样在开发者的角度就不需求额外的转化逻辑。
下面咱们模仿一条数据刺进到表中,看看表中保存的数据呈现的是何种样式,先模仿刺进操作:
val authorList = listOf<AuthorDetail>(
AuthorDetail(1, "taonce", "今日", "今日"),
AuthorDetail(2, "taonce2", "今日", "今日"),
)
val articleEntity =
ArticleEntity(1, "article", "android.com", "taonce", "taonce", authorList)
authorDao.insertAuthor(articleEntity)
此刻经过App Inspection
来看下表的数据
和咱们预期的是共同的,它在表中的具体方式便是一个Gson字符串。
文章结尾
本次篇幅暂时就介绍以上内容,篇幅过长阅读起来会发生疲倦感,后面的数据库晋级操作和技巧会另起一篇文章具体介绍,这次就到这了~