JetPack Room的基本使用
今天和大家一起来学Jetpack库中Room数据库的使用,之前也使用过Litepal、greenDao的第三方ORM(对象关系映射)数据库 。以后准备在项目中使用JetPack全家桶,Room是google官方推出的ORM数据库,势必要学习一波。
技术栈
本项目中用到的技术栈
- kotlin
- 协程
- viewbinding
项目前期准备
java
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
// room需要
apply plugin: 'kotlin-kapt'
android {
...
// viewBinding开启,注意必须是android studio3.6以上
viewBinding {
enabled = true
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// room所需依赖
implementation "androidx.room:room-runtime:2.2.2"
kapt "androidx.room:room-compiler:2.2.2"
implementation "androidx.room:room-ktx:2.2.2"
// 协程
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
// 列表主要是用于显示数据库的数据
implementation "androidx.recyclerview:recyclerview:1.1.0"
}
这里主要是项目中用到的各种依赖
Room介绍
markdown
Room 包含 3 个主要组件:
- 数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用 @Database 注释的类应满足以下条件:
- 是扩展 RoomDatabase 的抽象类。
- 在注释中添加与数据库关联的实体列表。
- 包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 的实例。
- Entity:表示数据库中的表。
- DAO:包含用于访问数据库的方法。
应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。 最后,应用使用实体来获取和设置与数据库中的表列相对应的值。
1.创建实体类
kotlin
package com.example.dataroom
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class User(var name:String,var age:Int) {
@PrimaryKey(autoGenerate = true)
var id:Long = 0
}
这里我们创建一个User的实体类,并添加@Entity的注解,添加主键并且设置为自增。
2.创建UserDao接口
kotlin
package com.example.dataroom
import androidx.room.*
@Dao
interface UserDao {
@Insert
fun insertUser(user: List<User>)
@Query("select * from User")
fun queryAllUser(): List<User>
@Query("select * from User where age > :age")
fun sectionUser(age: Int): List<User>
@Update
fun updateUser(user: List<User>)
@Delete
fun deleteUser(user: User)
@Query("delete from User")
fun deleteAllUser()
}
创建UserDao接口,并且添加@Dao注册,可以看到这里面就是执行对表增、删、改、查。有@Insert、 @Query、@Update、@Delete注解。如果你sql语句比较熟练,将会事半功倍。
3.编写database
kotlin
package com.example.dataroom
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(version = 1, entities = [User::class])
abstract class AppDataBase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: AppDataBase? = null
@Synchronized
fun getDataBase(context: Context): AppDataBase {
instance?.let {
return it
}
return Room.databaseBuilder(context, AppDataBase::class.java, "dataroom")
.build().apply {
instance = this
}
}
}
}
这里我们创建了一个AppDataBase的抽象类,并添加@DataBase注解其中version指定当前数据库的版本,entites指定实体类,多个实体类用逗号隔开。我们又定义了一个获取UserDao的抽象方法,通过companion object定义内部的静态方法,用于获取AppDataBase的实例,这里使用了单例模式,保证频繁获取AppDataBase实例的时候,AppDataBase只创建一次。
4.编写页面
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">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintTop_toTopOf="parent"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
/>
<Button
android:id="@+id/insertUserBtn"
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="1"
android:layout_height="wrap_content"
android:text="插入用户"
app:layout_constraintTop_toBottomOf="@id/toolBar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/deleteUserBtn"
/>
<Button
android:id="@+id/deleteUserBtn"
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="1"
android:layout_height="wrap_content"
android:text="删除用户"
app:layout_constraintTop_toTopOf="@id/insertUserBtn"
app:layout_constraintLeft_toRightOf="@id/insertUserBtn"
app:layout_constraintRight_toLeftOf="@id/updateUserBtn"
/>
<Button
android:id="@+id/updateUserBtn"
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="1"
android:layout_height="wrap_content"
android:text="修改用户"
app:layout_constraintTop_toTopOf="@id/insertUserBtn"
app:layout_constraintLeft_toRightOf="@id/deleteUserBtn"
app:layout_constraintRight_toLeftOf="@id/queryUserBtn"
/>
<Button
android:id="@+id/queryUserBtn"
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="1"
android:layout_height="wrap_content"
android:text="查询用户"
app:layout_constraintTop_toTopOf="@id/insertUserBtn"
app:layout_constraintLeft_toRightOf="@id/updateUserBtn"
app:layout_constraintRight_toRightOf="parent"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/insertUserBtn"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
5.编写逻辑
kotlin
package com.example.dataroom
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.dataroom.databinding.ActivityMainBinding
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
private val tag = "MainActivity"
private lateinit var activityMainBinding:ActivityMainBinding
private val coroutineScope by lazy {
CoroutineScope(Dispatchers.Main)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding= ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(activityMainBinding.root)
activityMainBinding.toolBar.title = "JetPack Room数据库"
activityMainBinding.toolBar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
setSupportActionBar(toolBar)
activityMainBinding.toolBar.setNavigationOnClickListener {
finish()
}
activityMainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
initData()
}
private fun initData() {
// 批量插入用户
activityMainBinding.insertUserBtn.setOnClickListener {
val users = ArrayList<User>()
for (i in 0..10){
users.add( User("张三${i}",i))
}
coroutineScope.launch {
val deffer = async(Dispatchers.IO) {
AppDataBase.getDataBase(this@MainActivity).userDao().insertUser(users)
}
}
}
// 批量删除用户
activityMainBinding.deleteUserBtn.setOnClickListener {
coroutineScope.launch {
async(Dispatchers.IO) {
AppDataBase.getDataBase(this@MainActivity).userDao().deleteAllUser()
}
}
}
// 批量修改数据
activityMainBinding.updateUserBtn.setOnClickListener {
coroutineScope.launch {
async(Dispatchers.IO) {
var users = AppDataBase.getDataBase(this@MainActivity).userDao().queryAllUser()
for (index in users.indices){
var user = users[index]
user.name = "李四$index"
user.age = index+24
}
AppDataBase.getDataBase(this@MainActivity).userDao().updateUser(users)
}
}
}
// 批量查询用户
activityMainBinding.queryUserBtn.setOnClickListener {
coroutineScope.launch {
val deffer = async(Dispatchers.IO) {
AppDataBase.getDataBase(this@MainActivity).userDao().queryAllUser()
}
val users = deffer.await()
activityMainBinding.recyclerView.adapter = UserAdapter(users)
}
}
}
}
这里没什么好讲的就是简单的增、删、改、查。每一个布局文件都会生成对应的Biding类,通过ActivityMainBinding.inflate(LayoutInflater.from(this))设置进来,再赋值给setContentView即可。
6.列表页适配器编写
kotlin
package com.example.dataroom
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.dataroom.databinding.UserItemBinding
class UserAdapter(var users:List<User>): RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
inner class UserViewHolder(userItemBinding: UserItemBinding):RecyclerView.ViewHolder(userItemBinding.root) {
val userNameTv = userItemBinding.userNameTv
val userAgeTv = userItemBinding.userAgeTv
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
return UserViewHolder(UserItemBinding.inflate(LayoutInflater.from(parent.context),parent,false))
}
override fun getItemCount(): Int = users.size
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
var user = users[position]
holder.userNameTv.text = user.name
holder.userAgeTv.text = user.age.toString()
}
}
7.数据库的升级
1.比如我又增加了一个Book类,并且在User类中增加了一个hobby爱好的字段
kotlin
package com.example.dataroom
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Book(val bookName:String,val author:String, val price:Double){
@PrimaryKey(autoGenerate = true)
var id:Long=0
}
kotlin
package com.example.dataroom
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class User(var name:String,var age:Int,var hobby:String) {
@PrimaryKey(autoGenerate = true)
var id:Long = 0
}
2.修改AppDataBase类
kotlin
package com.example.dataroom
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
// 修改版本, 添加新增的实体类的Class
@Database(version = 2, entities = [User::class,Book::class])
abstract class AppDataBase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: AppDataBase? = null
//编写增加实体类和修改类的两条sql语句
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table User add column hobby text not null default 'unknown'")
database.execSQL("create table Book(id integer primary key autoincrement not null,bookName text not null,author text not null,price real not null)")
}
}
@Synchronized
fun getDataBase(context: Context): AppDataBase {
instance?.let {
return it
}
return Room.databaseBuilder(context, AppDataBase::class.java, "dataroom")
//执行更新
.addMigrations(MIGRATION_1_2)
.build().apply {
instance = this
}
}
}
}
需要注意的是,如果是删除原来类中多余的字段,需要新建一个临时表将数据迁移过去,再将原来的表删除,再将这个临时表重命名为原来表的名字,这个方法可以自行百度。
8.总结
总的来说Room和greenDao很像,不过room更多的是原生的sql语句操作,所以需要对sql语句有一定的功底。还有就是感觉数据库升级功能不是很友好,频繁的升级我要不断的编写升级语句,而且这里 .addMigrations(MIGRATION_1_2,MIGRATION_2_3)一定是追加的操作,希望google可以后续改进一下。