持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,[点击查看活动详情]
Jetpack从入门到几乎入门(四)
(juejin.cn/post/714765… "juejin.cn/post/714765…")
前言
Jetpack系列:
Jetpack从入门到几乎入门(一) - 掘金 (juejin.cn)
Jetpack从入门到几乎入门(二) - 掘金 (juejin.cn)
Jetpack从入门到几乎入门(三) - 掘金 (juejin.cn)
本文是我在学习guolin大神的《第一行代码》第三版Jetpack部分的Room的知识总结,文中部分代码参考自《第一行代码》第三版
在阅读本文前,您需要掌握kotlin语言的基本语法
Room简介
在学习Room之前,每当提起数据库操作,我们总是会想到SQLite。SQLite虽然简单易用,但是如果放到大型项目中却会让项目的代码变得混乱。而Room是基于SQLite上提供的一个抽象层,它不仅支持SQL语句,而且还有很多便捷的功能:
- 具备SQL语句高亮和编译期检查
- 通过简单的注解完成接口的定义,易上手
- 支持协程、RxJava
- 可以搭配AndroidStudio自带的数据库查看窗口使用
Room的增删改查操作
使用Room前要在build.gradle上添加依赖
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'
我们先简单了解一下Room的三个组成部分
-
Entity :用于定义封装实际数据的实体类,每个实体类都在数据库中有一张对应的自动生成的表
-
Dao :Dao 指数据访问对象,对数据库各项操作的封装在此处进行,在实际编程中,逻辑层就不需要和底层数据库打交道,而是接和Dao 层进行交互
-
Database :用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提
供Dao 层的访问实例。
接下来我们创建一个实体类:
@Entity
data class User(var firstName: String, var lastName: String, var age: Int){
@PrimaryKey(autoGenerate = true)
var id:Long = 0
}
在类名上使用@Entity注解,然后加入id字段,使用@PrimaryKey注解将它设为主键。autoGenerate的值设为true,主键的值为自动生成。
接下来开始定义Dao,在这里封装访问数据库的操作(增删改查)。
新建一个UserDao接口
@Dao
interface UserDao {
@Insert
fun insertUser(user: User): Long
@Update
fun updateUser(newUser: User)
@Query("select * from User")
fun loadAllUsers(): List<User>
@Query("select * from User where age > :age")
fun loadUsersOlderThan(age: Int): List<User>
@Delete
fun deleteUser(user: User)
@Query("delete from User where lastName = :lastName")
fun deleteUserByLastName(lastName: String): Int
}
首先,我们要在接口前用上@Dao注解,接着我们进行对增删改查操作进行封装。Room为我们提供了相应的注解@Insert、@Delete、@Update和@Query。
唯一需要写SQL语句的地方是在查询操作,我们必须告知Room需要查询什么地方,其他时候我们直接使用注解标识即可。这样我们就大体定义了添加用户、修改用户数据、查询用户、删除用户这几种数据库操作接口。
最后一个环节是定义Database,新建一个AppDatabase.kt 文件
@Database(version = 1, entities = [User::class]) //声明数据库的版本号,包含哪些实体类,多个实体类之间可用逗号隔开
abstract class AppDatabase : RoomDatabase(){
abstract fun userDao(): UserDao //提供抽象方法,用于获取已编写的Dao实例;只声明方法,具体实现在Room底层自动完成
companion object { //Kotlin的类中不允许你声明静态成员或方法,要添加Companion对象来包装这些静态引用
private var instance: AppDatabase? = null
@Synchronized
fun getDatabase(context: Context) : AppDatabase {
instance?.let {
return it //instance变量不为空直接返回
}
//否则就调用Room.databaseBuilder()方法来构建一个AppDatabase的实例
return Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java, "app_database")
.build().apply {
instance = this
}
}
}
}
接下来我们修改页面,加上增删改查的4个按钮
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
<Button
android:id="@+id/getUserBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Get User"/>
<Button
android:id="@+id/addDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Add Data"/>
<Button
android:id="@+id/updateDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Update Data"/>
<Button
android:id="@+id/deleteDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Delete Data"/>
<Button
android:id="@+id/queryDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Query Data"/>
</LinearLayout>
在MainActivity中实现代码的逻辑
class MainActivity : AppCompatActivity() {
val userDao = AppDatabase.getDatabase(this).userDao()
val user1 = User("Tom", "Brady", 40)
val user2 = User("Tom", "Hanks", 63)
val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
...
binding.addDataBtn.setOnClickListener {
thread {
user1.id = userDao.insertUser(user1)
user2.id = userDao.insertUser(user2) //更新ID
}
}
binding.updateDataBtn.setOnClickListener {
thread {
user1.age = 42
userDao.updateUser(user1)
}
}
binding.deleteDataBtn.setOnClickListener {
thread {
userDao.deleteUserByLastName("Hanks")
}
}
binding.queryDataBtn.setOnClickListener {
thread {
for (user in userDao.loadAllUsers()) {
Log.d("MainActivity", user.toString())
}
}
}
...
}
值得注意的是,数据库操作是耗时操作,所以Room默认不能在主线程中操作,但是Room也提供了方法允许我们在主线程进行操作:
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java,"app_database")
.allowMainThreadQueries()
.build()
不过此方法只建议在测试环境下使用。
Room的数据库更新
进行数据库升降级时,我们可以使用addMigration进行操作,在AppDatabase的getDatabase中编写instance:
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"user.db"
).addMigrations(object :Migration(1,2){ //从版本1到版本2,降级则倒转
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE user ADD age INTEGER Default 0 not null ")
}
})allowMainThreadQueries().build()
Room还提供了两个方法:
fallbackToDestructiveMigration:升级时可使用该方法,当未匹配到版本的时候就会直接删除表然后重新创建。fallbackToDestructiveMigrationOnDowngrade:降级时可添加此方法,当未匹配到版本的时候就会直接删除表然后重新创建。
总结
学会