前言:
在咱们日常开发中,常常要和数据打交道,所以存储数据是很重要的事。Android从最开始应用SQLite作为数据库存储数据,再到许多的开源的数据库,例如QRMLite,DBFlow,郭霖大佬开发的Litepal等等,都是为了不便SQLite的应用而呈现的,因为SQLite的应用繁琐且容易出错。Google当然也意识到了SQLite的一些问题,于是在Jetpack组件中推出了Room,实质上Room也是在SQLite上提供了一层封装。因为它官网组件的身份,和良好的开发体验,当初逐步成为了最支流的数据库ORM框架。
Room官网文档:https://developer.android.goo...
SQL语法教程:https://www.runoob.com/sqlite...
本文代码地址:https://github.com/taxze6/Jet...
为什么要应用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...,不过
Room
容许通过外键来示意Entity
之间的关系。ForeignKey
咱们文章前面再谈,先讲简略的应用。 - @Embedded :实体类中援用其余实体类,在某些状况下,对于一张表的数据,咱们用多个
POJO
类来示意,所以在这种状况下,咱们能够应用Embedded
注解嵌套对象。
③创立数据拜访对象(Dao)解决增删改查
@Daointerface 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中开始反对主动迁徙,不过很多敌人反馈还是有很多问题,倡议手动迁徙,当然如果你应用的是更低的版本只能手动迁徙啦。
具体信息请参考:https://developer.android.goo...
具体如何降级数据库呢?上面咱们一步一步来实现吧!
①批改数据库版本
在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
@Daointerface 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: Buttonprivate 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 插入
@Daointerface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertUsers(vararg userEntity: UserEntity)}
其中onConflict
用于设置当事务中遇到抵触时的策略。
有如下一些参数能够抉择:
- OnConflictStrategy.REPLACE : 替换旧值,持续以后事务
- OnConflictStrategy.NONE : 疏忽抵触,持续以后事务
- OnConflictStrategy.ABORT : 回滚
④@Query 指定参数查问
每次都查表的全副信息这也不是事啊,咱们要用到where条件来指定参数查问。
@Daointerface UserDao { @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<UserEntity>}
大家能够本人学习一下SQL语法~
⑤多表查问
很多业务状况下,咱们是须要同时在多张表中进行查问的。
@Daointerface 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.goo...
⑨类型转换器 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
@Daointerface 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 缓存封装
.....