前言:

在咱们日常开发中,常常要和数据打交道,所以存储数据是很重要的事。Android从最开端运用SQLite作为数据库存储数据,再到许多的开源的数据库,例如QRMLite,DBFlow,郭霖大佬开发的Litepal等等,都是为了便利SQLite的运用而呈现的,由于SQLite的运用繁琐且简略犯错。Google当然也意识到了SQLite的一些问题,所以在Jetpack组件中推出了Room,本质上Room也是在SQLite上供给了一层封装。由于它官方组件的身份,和良好的开发体会,现在逐步成为了最主流的数据库ORM结构。

Room官方文档:developer.android.google.cn/jetpack/and…

SQL语法教程:www.runoob.com/sqlite/sqli…

本文代码地址:github.com/taxze6/Jetp…

为什么要运用Room?Room具有什么优势?

Room在SQLite上供给了一个抽象层,以便在充分利用SQLite的强壮功用的一起,能够享有更强健的数据库拜访机制。

Room的具体优势:

  • 有能够最大极限减少重复和简略犯错的样板代码的注解
  • 简化了数据库搬迁途径
  • 针对编译期SQL的语法检查
  • API规划友好,更简略上手,了解
  • SQL句子的运用愈加贴近,能够降低学习成本
  • RxJavaLiveDataKotlin协程等都支撑

Room具有三个主要模块

  • Entity: Entity用来表示数据库中的一个表。需求运用@Entity(tableName = "XXX")注解,其间的参数为表名。
  • Dao: 数据库拜访对象,用于拜访和办理数据(增修正查)。在运用时需求@DAO注解
  • Database: 它作为数据库持有者,用@Database注解和Room Database扩展的类

怎么运用Room呢?

①增加依赖
最近更新时间(文章发布时的最新版别) 稳定版 Alpha 版
2022 年 6 月 1 日 2.4.2 2.5.0-alpha02
plugins {
   ...
  id 'kotlin-kapt'
}
​
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
kapt 'androidx.room:room-compiler:$room_version'
②创立Entity实体类,用来表示数据库中的一张表(table)
@Entity(tableName = "user")
data class UserEntity(
  //主键界说需求用到@PrimaryKey(autoGenerate = true)注解,autoGenerate参数决议是否自增加
  @PrimaryKey(autoGenerate = true) val id:Int = 0, //默认值为0
  @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT) val name:String?,
  @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER) val age:Int?
)

其间,每个表的字段都要加上@ColumnInfo(name = "xxx", typeAffinity = ColumnInfo.xxx)name特点表示这张表中的字段名,typeAffinity表示改字段的数据类型。

其他常用注解:

  • @Ignore :Entity中的所有特点都会被耐久化到数据库,除非运用@Ignore

    @Ignore val name: String?
    
  • @ForeignKey:外键束缚,不同于目前存在的大多数ORM库,Room不支撑Entitiy对象间的直接引用。Google也做出了解说,具体原因请检查:developer.android.com/training/da…,不过Room允许经过外键来表示Entity之间的联系。ForeignKey咱们文章后边再谈,先讲简略的运用。

  • @Embedded :实体类中引用其他实体类,在某些情况下,对于一张表的数据,咱们用多个POJO类来表示,所以在这种情况下,咱们能够运用Embedded注解嵌套对象。

③创立数据拜访对象(Dao)处理增修正查
@Dao
interface UserDao {
  //增加用户
  @Insert
  fun addUser(vararg userEntity: UserEntity)//删去用户
  @Delete
  fun deleteUser(vararg userEntity: UserEntity)//更新用户
  @Update
  fun updateUser(vararg userEntity: UserEntity)//查找用户
  //回来user表中所有的数据
  @Query("select * from user")
  fun queryUser(): List<UserEntity>
}

Dao担任供给拜访DBAPI,咱们每一张表都需求一个Dao。在这儿运用@Dao注解界说Dao类。

  • @Insert, @Delete需求传一个entity()进去
  • Class<?> entity() default Object.class;
    
  • @Query则是需求传递SQL句子
  • public @interface Query {
        //要运行的SQL句子
      String value();
    }
    

☀留意:Room会在编译期基于Dao主动生成具体的完成类,UserDao_Impl(完成增修正查的办法)。

Dao所有的办法调研都在当时线程进行,需求防止在UI线程中直接拜访!

④创立Room database
@Database(entities = [UserEntity::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
  abstract fun userDao(): UserDao
}

经过Room.databaseBuilder()或许 Room.inMemoryDatabaseBuilder()获取Database实例

val db = Room.databaseBuilder(
  applicationContext,
  UserDatabase::class.java, "userDb"
   ).build()

☀留意:创立Database的成本较高,所以咱们最好运用单例的Database,防止重复创立实例所带来的开销。

单例模式创立Database:

@Database(entities = [UserEntity::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
  abstract fun getUserDao(): UserDao
​
  companion object {
    @Volatile
    private var INSTANCE: UserDatabase? = null@JvmStatic
    fun getInstance(context: Context): UserDatabase {
      val tmpInstance = INSTANCE
      if (tmpInstance != null) {
        return tmpInstance
       }
      //锁
      synchronized(this) {
        val instance =
          Room.databaseBuilder(context, UserDatabase::class.java, "userDb").build()
        INSTANCE = instance
        return instance
       }
     }
   }
}
​
⑤在Activity中运用,进行一些可视化操作

activity_main:

<LinearLayout
  ...
  tools:context=".MainActivity"
  android:orientation="vertical">
  <Button
    android:id="@+id/btn_add"
    ...
    android:text="增加一条数据"/>
  <Button
    android:id="@+id/btn_delete"
    ...
    android:text="删去一条数据"/>
  <Button
    android:id="@+id/btn_update"
    ...
    android:text="更新一条数据"/>
  <Button
    android:id="@+id/btn_query_all"
    ...
    android:text="查新所有数据"/>
</LinearLayout>

MainActivity:

private const val TAG = "My_MainActivity"
class MainActivity : AppCompatActivity() {
  private val userDao by lazy {
    UserDatabase.getInstance(this).getUserDao()
   }
  private lateinit var btnAdd: Button
  private lateinit var btnDelete: Button
  private lateinit var btnUpdate: Button
  private lateinit var btnQueryAll: Button
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    init()
    //增加数据
    btnAdd.setOnClickListener {
      //数据库的增修正查必须在子线程,当然也能够在协程中操作
      Thread {
        val entity = UserEntity(name = "Taxze", age = 18)
        userDao.addUser(entity)
       }.start()
     }
    //查询数据
    btnQueryAll.setOnClickListener {
      Thread {
        val userList = userDao.queryUser()
        userList.forEach {
          Log.d(TAG, "查询到的数据为:$it")
         }
       }.start()
     }
    //修正数据
    btnUpdate.setOnClickListener {
      Thread {
        userDao.updateUser(UserEntity(2, "Taxzeeeeee", 18))
       }.start()
     }
        //删去数据
    btnDelete.setOnClickListener {
      Thread {
        userDao.deleteUser(UserEntity(2, null, null))
       }.start()
     }
   }
  //初始化
  private fun init() {
    btnAdd = findViewById(R.id.btn_add)
    btnDelete = findViewById(R.id.btn_delete)
    btnUpdate = findViewById(R.id.btn_update)
    btnQueryAll = findViewById(R.id.btn_query_all)
   }
}

结果:

Jetpack Room — 给你一种新的数据库操作体会!
到这儿咱们现已讲完了Room的最基本的运用,如果只是一些非常简略的事务,你看到这儿现已能够去写代码了,可是还有一些进阶的操作需讲解一下,持续往下看吧!

数据库的晋级

Room在2021 年 4 月 21 日发布的版别 2.4.0-alpha01中开端支撑主动搬迁,不过许多朋友反应仍是有许多问题,建议手动搬迁,当然如果你运用的是更低的版别只能手动搬迁啦。

具体信息请参考:developer.android.google.cn/training/da…

具体怎么晋级数据库呢?下面咱们一步一步来完成吧!

①修正数据库版别

UserDatabase文件中修正version,将其变为2(原来是1)

在此刻,咱们需求想一想,咱们要对数据库做什么晋级操作呢?

咱们这儿为了演示就给数据库增加一张成绩表:

@Database(entities = [UserEntity::class,ScoreEntity::class], version = 2)

增加表:

@Entity(tableName = "score")
data class ScoreEntity(
  @PrimaryKey(autoGenerate = true) var id: Int = 0,
  @ColumnInfo(name = "userScore")
  var userScore: Int
)
②创立对应的Dao,ScoreDao
@Dao
interface ScoreDao {
  @Insert
  fun insertUserScore(vararg scoreEntity: ScoreEntity)@Query("select * from score")
  fun queryUserScoreData():List<ScoreEntity>
}
③在Database中增加搬迁
@Database(entities = [UserEntity::class,ScoreEntity::class], version = 2)
abstract class UserDatabase : RoomDatabase() {
  abstract fun getUserDao(): UserDao
  //增加一个Dao
  abstract fun getScoreDao():ScoreDao
​
  companion object {
    //变量名最好为xxx版别搬迁到xxx版别
    private val MIGRATION_1_2 = object : Migration(1, 2) {
      override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(
          """
          create table userScore(
          id integer primary key autoincrement not null,
          userScore integer not null)
        """.trimIndent()
         )
       }
     }
​
    @Volatile
    private var INSTANCE: UserDatabase? = null@JvmStatic
    fun getInstance(context: Context): UserDatabase {
            ...
      synchronized(this) {
        val instance =
          Room.databaseBuilder(
            context.applicationContext,
            UserDatabase::class.java,
            "userDb"
           )
             .addMigrations(MIGRATION_1_2)
             .build()
        INSTANCE = instance
        return instance
       }
     }
   }
}
④运用更新后的数据

在xml布局中增加两个Button:

<Button
  android:id="@+id/btn_add_user_score"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="增加user的score数据"/><Button
  android:id="@+id/btn_query_user_score"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="查询user的score数据"/>

在MainActivity中加入:

private val userScoreDao by lazy {
  UserDatabase.getInstance(this).getScoreDao()
}
​
...
​
private lateinit var btnAddUserScore: Button
private lateinit var btnQueryUserScore: Button
​
...
btnAddUserScore = findViewById(R.id.btn_add_user_score)
btnQueryUserScore = findViewById(R.id.btn_query_user_score)
​
...
btnAddUserScore.setOnClickListener {
      Thread{
        userScoreDao.insertUserScore(ScoreEntity(userScore = 100))
       }.start()
     }
​
btnQueryUserScore.setOnClickListener {
      Thread{
        userScoreDao.queryUserScoreData().forEach{
          Log.d(TAG,"userScore表的数据为:$it")
         }
       }.start()
     }

这样对数据库的一次手动搬迁就完成啦!

如果你想持续晋级,就重复之前的步骤,然后将2→3

private val MIGRATION_2_3 = object : Migration(2, 3) {
  override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL(
      """
      .... 再一次新的操作
    """.trimIndent()
     )
   }
}
​
...
.addMigrations(MIGRATION_1_2,MIGRATION_2_3)

运用Room更多的骚操作!

①想知道更多的Room数据库搬迁的操作吗,那你能够看看这篇文章:

www.modb.pro/db/139101

②更优雅的修正数据

在上面的修正数据操作中,咱们是需求填入每个字段的值的,可是,大部分情况,咱们是不会悉数知道的,比方咱们不知道Userage,那么咱们的age字段就填个Null吗?

val entity = UserEntity(name = "Taxze", age = null)

这显然是不合适的!

当咱们只想修正用户名的时,却又不知道age的值的时候,咱们需求怎么修正呢?

⑴创立UpdateNameBean

class UpdateNameBean(var id:Int,var name:String)

⑵在Dao中加入新的办法

@Update(entity = UserEntity::class)
fun updataUser2(vararg updataNameBean:UpdateNameBean)

⑶然后在运用时只需求传入id,和name即可

userDao.updateUser2(updataNameBean(2, "Taxzeeeeee"))

当然你也能够给用户类创立多个构造办法,并给这些构造办法增加@lgnore

③详解@Insert 刺进
@Dao
interface UserDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  fun insertUsers(vararg userEntity: UserEntity)
}

其间onConflict用于设置当事务中遇到抵触时的策略。

有如下一些参数能够挑选:

  • OnConflictStrategy.REPLACE : 替换旧值,持续当时事务
  • OnConflictStrategy.NONE : 疏忽抵触,持续当时事务
  • OnConflictStrategy.ABORT : 回滚
④@Query 指定参数查询

每次都查表的悉数信息这也不是事啊,咱们要用到where条件来指定参数查询。

@Dao
interface UserDao {
  @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
  fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<UserEntity>
}

大家能够自己学习一下SQL语法~

⑤多表查询

许多事务情况下,咱们是需求一起在多张表中进行查询的。

@Dao
interface UserDao {
  @Query(
    "SELECT * FROM user " +
        "INNER JOIN score ON score.id = user.id " +
        "WHERE user.name LIKE :userName"
   )
  fun findUsersScoreId(userName: String): List<UserEntity>
}
⑥@Embedded内嵌对象

咱们能够运用@Embedded注解,将一个Entity作为特点内嵌到别的一个Entity,然后咱们就能够像拜访Column相同去拜访内嵌的Entity啦。

data class Score(
  val id:Int?,
  val score:String?,
)
@Entity(tableName = "user")
data class UserEntity(
  @PrimaryKey(autoGenerate = true) val id:Int = 0,
   .....
  @Embedded val score: Score?
)
⑦运用@Relation 注解和 foreignkeys注解来描述Entity之间更复杂的联系

能够完成一对多,多对多的联系

⑧预填充数据库

能够检查官方文档:developer.android.google.cn/training/da…

⑨类型转换器 TypeConverter

….

Room合作LiveData和ViewModel

下面咱们经过一个Room+LiveData+ViewModel的例子来完成这篇文章的学习吧

话不多说,先上效果图:

Jetpack Room — 给你一种新的数据库操作体会!

①创立UserEntity
@Entity(tableName = "user")
data class UserEntity(
  @PrimaryKey(autoGenerate = true) val id: Int = 0,
  @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT) val name: String?,
  @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER) val age: Int?,
)
②创立对应的Dao
@Dao
interface UserDao {
  //增加用户
  @Insert
  fun addUser(vararg userEntity: UserEntity)//查找用户
  //回来user表中所有的数据,运用LiveData
  @Query("select * from user")
  fun getUserData(): LiveData<List<UserEntity>>
}
③创立对应的Database

代码在最开端的例子中现已给出了。

④创立ViewModel
class UserViewModel(userDao: UserDao):ViewModel(){
  var userLivedata = userDao.getUserData()
}
⑤创立UserViewModelFactory

咱们在UserViewModel类中传递了UserDao参数,所以咱们需求有这么个类完成ViewModelProvider.Factory接口,以便于将UserDao在实例化时传入。

class UserViewModelFactory(private val userDao: UserDao) : ViewModelProvider.Factory {
  override fun <T : ViewModel?> create(modelClass: Class<T>): T {
    return UserViewModel(userDao) as T
   }
}
⑥编辑xml

activity_main:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  tools:context=".MainActivity">
​
  <EditText
    android:id="@+id/user_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="请输入UserName" />
​
  <EditText
    android:id="@+id/user_age"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="请输入UserAge" />
​
  <Button
    android:id="@+id/btn_add"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="增加一条user数据" />
​
  <ListView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  </ListView>
</LinearLayout>

创立一个simple_list_item.xml,用于展现每一条用户数据

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/userText"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" />
⑦在MainActivity中调用
class MainActivity : AppCompatActivity() {
  private var userList: MutableList<UserEntity> = arrayListOf()
  private lateinit var arrayAdapter: ArrayAdapter<UserEntity>
  private val userDao by lazy {
    UserDatabase.getInstance(this).getUserDao()
   }
  lateinit var viewModel: UserViewModel
  private lateinit var listView: ListView
  private lateinit var editUserName: EditText
  private lateinit var editUserAge: EditText
  private lateinit var addButton: Button
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    init()
    arrayAdapter = ArrayAdapter(this, R.layout.simple_list_item, userList)
    listView.adapter = arrayAdapter
    //实例化UserViewModel,并监听LiveData的变化。
    viewModel =
      ViewModelProvider(this, UserViewModelFactory(userDao)).get(UserViewModel::class.java)
    viewModel.userLivedata.observe(this, Observer {
      userList.clear()
      userList.addAll(it)
      arrayAdapter.notifyDataSetChanged()
     })
    addButton.setOnClickListener {
      addClick()
     }
   }
​
  //初始化控件
  private fun init() {
    editUserName = findViewById(R.id.user_name)
    editUserAge = findViewById(R.id.user_age)
    addButton = findViewById(R.id.btn_add)
    listView = findViewById(R.id.recycler_view)
   }
​
  fun addClick() {
    if (editUserName.text.toString() == "" || editUserAge.text.toString() == "") {
      Toast.makeText(this, "姓名或年龄不能为空", Toast.LENGTH_SHORT).show()
      return
     }
    val user = UserEntity(
      name = editUserName.text.toString(),
      age = editUserAge.text.toString().toInt()
     )
    thread {
      userDao.addUser(user)
     }
   }
}

这样一个简略的Room合作LiveData和ViewModel完成页面主动更新的Demo就完成啦具体代码能够检查Git库房

尾述

看完这篇文章,相信你现已发现Room虽然看上去仍是有些繁琐,可是相比较于SQLite仍是简略不少了,Room还能帮你检测SQL是否正确哈哈。这篇文章现已很具体的讲了Jetpack Room的大部分用法,不过在看完文章后,你仍需多多实践,相信你很快就能够把握Room啦 由于我本人才能也有限,文章有不对的当地欢迎指出,有问题欢迎在评论区留言讨论~

关于我

Hello,我是Taxze,如果您觉得文章对您有价值,期望您能给我的文章点个❤️,也欢迎重视我的博客。

如果您觉得文章还差了那么点东西,也请经过重视督促我写出更好的文章——如果哪天我进步了呢?

根底系列:

2022 让我带你Jetpack架构组件从入门到通晓 — Lifecycle

学会运用LiveData和ViewModel,我相信会让你在写事务时变得轻松

当你真的学会DataBinding后,你会发现“这玩意真香”!

Navigation — 这么好用的跳转办理结构你确认不来看看?

在写事务时想要有更好的体会吗?那你无妨来看看Room这个结构 (本文)

以下部分还在码字,赶紧点个收藏吧

2022 让我带你Jetpack架构组件从入门到通晓 — Paging3

2022 让我带你Jetpack架构组件从入门到通晓 — WorkManager

2022 让我带你Jetpack架构组件从入门到通晓 — ViewPager2

2022 让我带你Jetpack架构组件从入门到通晓 — 登录注册页面实战(MVVM)

进阶系列:

协程 + Retrofit网络恳求状况封装

Room 缓存封装

…..

我正在参加技术社区创作者签约方案招募活动,点击链接报名投稿。