Jetpack Room
Posted 编程的平行世界
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Room相关的知识,希望对你有一定的参考价值。
前言:
🏀在我们日常开发中,经常要和数据打交道,所以存储数据是很重要的事。android从最开始使用SQLite作为数据库存储数据,再到许多的开源的数据库,例如QRMLite,DBFlow,郭霖大佬开发的Litepal等等,都是为了方便SQLite的使用而出现的,因为SQLite的使用繁琐且容易出错。Google当然也意识到了SQLite的一些问题,于是在Jetpack组件中推出了Room,本质上Room也是在SQLite上提供了一层封装。因为它官方组件的身份,和良好的开发体验,现在逐渐成为了最主流的数据库ORM框架。
🌟Room官方文档:https://developer.android.google.cn/jetpack/androidx/releases/room
🌟SQL语法教程:https://www.runoob.com/sqlite/sqlite-tutorial.html
🚀本文代码地址:https://github.com/taxze6/Jetpack_learn/tree/main/Jetpack_basic_learn/room
为什么要使用Room?Room具有什么优势?
Room在SQLite上提供了一个抽象层,以便在充分利用SQLite的强大功能的同时,能够享有更强健的数据库访问机制。
Room的具体优势:
- 有可以最大限度减少重复和容易出错的样板代码的注解
- 简化了数据库迁移路径
- 针对编译期
SQL
的语法检查 - API设计友好,更容易上手,理解
- 与
SQL
语句的使用更加贴近,能够降低学习成本 - 对
RxJava
、LiveData
、Kotlin
协程等都支持
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也做出了解释,具体原因请查看:https://developer.android.com/training/data-storage/room/referencing-data,不过
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
负责提供访问DB
的API
,我们每一张表都需要一个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)
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e35hYmSg-1657849942540)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/50e10c57e1fa41a6960b3c5d87fd9713~tplv-k3u1fbpfcp-watermark.image?)]
到这里我们已经讲完了Room的最基本的使用,如果只是一些非常简单的业务,你看到这里已经可以去写代码了,但是还有一些进阶的操作需讲解一下,继续往下看吧!
数据库的升级
Room在2021 年 4 月 21 日发布的版本 2.4.0-alpha01中开始支持自动迁移,不过很多朋友反应还是有很多问题,建议手动迁移,当然如果你使用的是更低的版本只能手动迁移啦。
具体信息请参考:https://developer.android.google.cn/training/data-storage/room/migrating-db-versions#manual
具体如何升级数据库呢?下面我们一步一步来实现吧!
①修改数据库版本
在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数据库迁移的操作吗,那你可以看看这篇文章:
https://www.modb.pro/db/139101
②更优雅的修改数据
在上面的修改数据操作中,我们是需要填入每个字段的值的,但是,大部分情况,我们是不会全部知道的,比如我们不知道User
的age
,那么我们的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
之间更复杂的关系
可以实现一对多,多对多的关系
⑧预填充数据库
可以查看官方文档:https://developer.android.google.cn/training/data-storage/room/prepopulate
⑨类型转换器 TypeConverter
…
Room配合LiveData和ViewModel
下面我们通过一个Room
+LiveData
+ViewModel
的例子来完成这篇文章的学习吧
话不多说,先上效果图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QGq7L7fj-1657849942542)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/106dc07d0e1c43b0a3b54e04d4e80678~tplv-k3u1fbpfcp-watermark.image?)]
①创建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
,用于展示每一条用户数据
以上是关于Jetpack Room的主要内容,如果未能解决你的问题,请参考以下文章
Android jetpack room SQLLite 数据库 修改字段
Android Jetpack Paging 3:带 Room 的 PagingSource
Android Jetpack: Room | 中文教学视频