Jetpack Room 初次尝试
Jetpack Room是Android官方提供的一个操作App SQLite数据库的框架。整体使用起来比较简单。
写了个简单的管理用户的Hello World。
环境
Android Studio 4.2
Kotlin 1.4.31
Kotlin Coroutines 1.5.0
Jetpack Room 2.3.0 官网传送门
- 依赖添加
def room_version = "2.3.0"
// 核心库
implementation "androidx.room:room-runtime:$room_version"
// 使用kapt(Kotlin annotation processing tool)处理注解
kapt "androidx.room:room-compiler:$room_version"
// 使用ksp(Kotlin Symbolic Processing)处理注解(速度会更快一点)
ksp("androidx.room:room-compiler:$room_version")
// Room协程支持
implementation("androidx.room:room-ktx:$room_version")
// Room RxJava2 支持
implementation "androidx.room:room-rxjava2:$room_version"
// Room RxJava3 支持
implementation "androidx.room:room-rxjava3:$room_version"
// Room Guava支持
implementation "androidx.room:room-guava:$room_version"
// Room测试
testImplementation("androidx.room:room-testing:$room_version")
// 协程
def coroutine_version = "1.5.0"
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
// 提供Android专属调度器
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
为啥要导入协程库呢?因为Room规定数据库操作不能在主线程上执行,因此这里引入了协程来处理这个问题,另外,Room是支持协程的,可以和协程结合起来使用。
Room三大组件
Room 包含三大组件:
- DataBase 数据库持有类,继承自RoomDatabase类,持有0个或多个获取DAO的抽象方法
- Entity 数据实体类,定义了数据库表的结构。
- DAO 数据操作接口,定义的操作数据的方法。
Hello World
来看看怎样写一个Hello World。
首先肯定得定义一个实体,就叫他User吧。
// 使用@Entity注解,标识这是一个Entity组件
@Entity
data class User(
// 主键,autoGenerate=true自动生成
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
// 列信息
@ColumnInfo(name = "name")
val name: String="",
@ColumnInfo(name = "gender")
val gender: String="",
@ColumnInfo(name = "age")
val age: String=""
)
有了实体,肯定得有操作实体的方法吧,再定义一个UserDao
// 标识这是一个DAO组件
@Dao
interface UserDao {
// 插入用户的注解,vararg表示可以一次插入多个用户
// onConflict标识插入遇到冲突时的解决策略,可选REPLACE,ROLLBACK,ABORD,FAIL,IGNORE,简单明了,看单词就能明白作用
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(vararg users: User)
// 删除用户的注解
@Delete
fun deleteUser(user: User)
// 删除所有用户的注解,使用Query注解可以执行自定义的SQL语句
@Query("DELETE FROM user")
fun deleteAll()
// 更新用户的注解,同样可以设置冲突策略
@Update(onConflict = OnConflictStrategy.REPLACE)
fun updateUser(user: User)
// 查询所有用户
@Query("SELECT * FROM user")
fun queryAllUser(): List<User>
// 根据名字查询用户,:name对应方法中的name参数
@Query("SELECT * FROM USER WHERE name=:name")
fun queryByName(name: String): User?
}
有了DAO,怎么使用DAO中的方法来操作数据库呢,还需要定义一个UserDataBase抽象类
// @DataBase标识这是一个Database组件,entities表示DataBase拥有的实体,可以传入多个,version用于数据库版本更新,这里填1,当然@Database还可以设置其它的参数,这里就不详解了
@Database(entities =[User::class,],version = 1)
abstract class UserDataBase :RoomDatabase(){
// 定义获取上面定义的UserDao的抽象方法
abstract fun userDao():UserDao
}
好了三大组件已经集齐,接下来就该实际上手操作了。
首先需要使用Room构造一个UserDataBase对象
private val dataBase: UserDataBase by lazy {
// 传入context,定义的UserDataBase以及数据库文件名,databaseBuilder还可以设置其它的一些内容,如从文件导入数据库,超时时间等,这里不做介绍。
Room.databaseBuilder(this, UserDataBase::class.java, "user.db").build()
}
因为Hell World只有在MainActivity中使用,因此就直接在MainActivity中使用by lazy创建,正常情况下请使用单例懒汉模式创建,因为频繁的创建DataBase开销是很高的。
有了dataBase对象,就可以直接获取到UserDao来操作数据了,从最简单的增删改查开始吧。
首先写一个创造用户的方法:
private fun createUser(): User {
val name = binding.etName.text.toString()
val gender = binding.etGender.text.toString()
val age = binding.etAge.text.toString()
return User(name = name, gender = gender, age = age)
}
很简单,就是读取输入框中的数据,构造一个用户,这里使用了ViewBinding来获取组件。
- 增
binding.btnInsert.setOnClickListener {
launch {
// Room操作数据库不能在主线程,因此这里使用IO线程操作
withContext(Dispatchers.IO) {
dataBase.userDao().insertUser(createUser())
}
// 插入后更新UI
updateView()
Toast.makeText(this@MainActivity, "Insert success", Toast.LENGTH_SHORT).show()
}
}
- 删
binding.btnDelete.setOnClickListener {
launch {
withContext(Dispatchers.IO) {
dataBase.userDao().deleteUser(createUser().copy(id=getUid()))
}
updateView()
Toast.makeText(this@MainActivity, "Delete success", Toast.LENGTH_SHORT).show()
}
}
- 删除全部
binding.btnDelete.setOnLongClickListener {
launch {
withContext(Dispatchers.IO) {
dataBase.userDao().deleteAll()
}
updateView()
Toast.makeText(this@MainActivity, "Delete all success", Toast.LENGTH_SHORT).show()
}
true
}
- 改
// 获取输入框中的UID
private fun getUid(): Long {
return try {
binding.etId.text.toString().toLong()
} catch (e: Exception) {
0L
}
}
binding.btnUpdate.setOnClickListener {
launch {
// 这里使用copy方法用构造的User创建一个新的User,并设置id
withContext(Dispatchers.IO) {
dataBase.userDao().updateUser(createUser().copy(id=getUid()))
}
updateView()
Toast.makeText(this@MainActivity, "Update success", Toast.LENGTH_SHORT).show()
}
}
- 查
binding.btnQuery.setOnClickListener {
launch {
// 根据名字查询用户,withContext会返回最后一行的内容,因此这里能够拿到用户信息
val user = withContext(Dispatchers.IO) {
dataBase.userDao().queryByName(binding.etName.text.toString())
}
user?.let {
binding.tvInfo.text = "${it.id}-${it.name} ${it.gender} ${it.age}"
}
Toast.makeText(this@MainActivity, "Query success", Toast.LENGTH_SHORT).show()
}
}
怎么样,是不是超级简单,Jetpack出的一些框架真的是懒人的福利啊。
最后放一下整体源码:
- MainACtivity
package com.ricky.room
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.room.Room
import com.ricky.room.databinding.ActivityMainBinding
import kotlinx.coroutines.*
import java.lang.Exception
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
private val dataBase: UserDataBase by lazy {
Room.databaseBuilder(this, UserDataBase::class.java, "user.db").build()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.btnInsert.setOnClickListener {
launch {
withContext(Dispatchers.IO) {
dataBase.userDao().insertUser(createUser())
}
updateView()
Toast.makeText(this@MainActivity, "Insert success", Toast.LENGTH_SHORT).show()
}
}
binding.btnDelete.setOnClickListener {
launch {
withContext(Dispatchers.IO) {
dataBase.userDao().deleteUser(createUser().copy(id=getUid()))
}
updateView()
Toast.makeText(this@MainActivity, "Delete success", Toast.LENGTH_SHORT).show()
}
}
binding.btnDelete.setOnLongClickListener {
launch {
withContext(Dispatchers.IO) {
dataBase.userDao().deleteAll()
}
updateView()
Toast.makeText(this@MainActivity, "Delete all success", Toast.LENGTH_SHORT).show()
}
true
}
binding.btnUpdate.setOnClickListener {
launch {
withContext(Dispatchers.IO) {
dataBase.userDao().updateUser(createUser().copy(id=getUid()))
}
updateView()
Toast.makeText(this@MainActivity, "Update success", Toast.LENGTH_SHORT).show()
}
}
binding.btnQuery.setOnClickListener {
launch {
val user = withContext(Dispatchers.IO) {
dataBase.userDao().queryByName(binding.etName.text.toString())
}
user?.let {
binding.tvInfo.text = "${it.id}-${it.name} ${it.gender} ${it.age}"
}
Toast.makeText(this@MainActivity, "Query success", Toast.LENGTH_SHORT).show()
}
}
updateView()
}
private fun getUid(): Long {
return try {
binding.etId.text.toString().toLong()
} catch (e: Exception) {
0L
}
}
private fun createUser(): User {
val name = binding.etName.text.toString()
val gender = binding.etGender.text.toString()
val age = binding.etAge.text.toString()
return User(name = name, gender = gender, age = age)
}
private fun updateView() {
launch {
val text = withContext(Dispatchers.IO) {
val users = dataBase.userDao().queryAllUser()
users.joinToString("\n") { "${it.id}-${it.name} ${it.gender} ${it.age}" }
}
binding.tvInfo.text = text
}
}
}
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="435dp" />
<EditText
android:id="@+id/et_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:hint="ID"
app:layout_constraintEnd_toStartOf="@id/et_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/guideline2" />
<EditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:hint="姓名"
app:layout_constraintEnd_toStartOf="@id/et_gender"
app:layout_constraintStart_toEndOf="@id/et_id"
app:layout_constraintTop_toBottomOf="@id/guideline2" />
<EditText
android:id="@+id/et_gender"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:hint="性别"
app:layout_constraintEnd_toStartOf="@id/et_age"
app:layout_constraintStart_toEndOf="@id/et_name"
app:layout_constraintTop_toBottomOf="@id/guideline2" />
<EditText
android:id="@+id/et_age"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:hint="年龄"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/et_gender"
app:layout_constraintTop_toBottomOf="@id/guideline2" />
<Button
android:id="@+id/btn_insert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="insert"
app:layout_constraintBottom_toTopOf="@+id/btn_update"
app:layout_constraintEnd_toStartOf="@id/btn_delete"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2" />
<Button
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="delete"
app:layout_constraintBottom_toTopOf="@id/btn_query"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_insert"
app:layout_constraintTop_toBottomOf="@id/guideline2" />
<Button
android:id="@+id/btn_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="14dp"
android:text="update"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_query"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_insert" />
<Button
android:id="@+id/btn_query"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="query"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_update"
app:layout_constraintTop_toBottomOf="@id/btn_delete" />
</androidx.constraintlayout.widget.ConstraintLayout>