前言:
在咱们日常开发中,常常要和数据打交道,所以存储数据是很重要的事。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
句子的运用愈加贴近,能够降低学习成本 - 对
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也做出了解说,具体原因请检查: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
担任供给拜访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)
}
}
结果:
到这儿咱们现已讲完了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
②更优雅的修正数据
在上面的修正数据操作中,咱们是需求填入每个字段的值的,可是,大部分情况,咱们是不会悉数知道的,比方咱们不知道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
之间更复杂的联系
能够完成一对多,多对多的联系
⑧预填充数据库
能够检查官方文档:developer.android.google.cn/training/da…
⑨类型转换器 TypeConverter
….
Room合作LiveData和ViewModel
下面咱们经过一个Room
+LiveData
+ViewModel
的例子来完成这篇文章的学习吧
话不多说,先上效果图:
①创立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 缓存封装
…..
我正在参加技术社区创作者签约方案招募活动,点击链接报名投稿。