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_entityDao类,将增修正查办法先界说好

@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.ABORTNONE效果共同,这儿就不过多介绍
  • 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方式,传入了ContextRoomDb和数据库称号,这儿仍是比较简单的,在后面涉及数据库晋级时,咱们仍是会回到此处,增加晋级操作。

到这儿数据库的准备工作现已完成了,接下来就能够实践一下最常用的增修正查操作了,趁便提一下,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中实时查看到数据的变化。

Android使用Jetpack Room管理数据库 - 第一弹

假如你想实时的调查表中数据变化,记住勾选Live updates框,未勾选的状况需求手动点击前面的改写图标。App Inspection还能够直接运用SQL句子来操作表,你能够在调试过程中修正或许模仿一些数据。

Android使用Jetpack Room管理数据库 - 第一弹

左面红框便是新建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字段的。

Android使用Jetpack Room管理数据库 - 第一弹

嵌套目标

当咱们界说的数据类中嵌套了别的一个或许多个数据类时,假如不做任何操作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来调查下表结构。

Android使用Jetpack Room管理数据库 - 第一弹

从上面的图片就能够看出被嵌入目标的字段也一起加入到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来看下表的数据

Android使用Jetpack Room管理数据库 - 第一弹

和咱们预期的是共同的,它在表中的具体方式便是一个Gson字符串。

文章结尾

本次篇幅暂时就介绍以上内容,篇幅过长阅读起来会发生疲倦感,后面的数据库晋级操作和技巧会另起一篇文章具体介绍,这次就到这了~