介绍
Room是一个对象关系映射库,拥有SQLite的全部功能,并且能更方便的访问数据库
Room有3个主要组成部分:
DataBase
:用注解标记一个类为数据库,该类需要继承RoomDataBase,可以通过Room.databaseBuilder获取实例Entity
:用注解标记一个类为表,类中的每个属性就是表中的字段Dao
:用注解标记一个接口为数据访问对象
这种方式和Java Web中的MyBatis很像
使用
- 引入依赖
def room_version = "2.2.1"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"
因为最近也在学Kotlin,所以这次的Demo准备用Kotlin写(写了好久,因为Kotlin不熟练。。。)
言归正传,如果要是用Kotlin,还需要在app/build.gradle
的开头加上以下部分:
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'apply plugin: 'kotlin-kapt'
这样就能够使用Kotlin来操作Room了
- 新建Entity
写一个实体类,比如User
,需要给该类加上@Entity
注解,并且给不同的属性加上不同的注解,就和创建数据库表一个道理
package com.example.roomdemo
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
class User(name: String?) {
@PrimaryKey(autoGenerate = true)
var id: Int = 0
@ColumnInfo(name = "user_name")
var name: String? = null
init {
this.name = name
}
constructor(id: Int, name: String?) : this(name) {
this.id = id
this.name = name
}
override fun toString(): String {
return "User(id=$id, name=$name)"
}
}
@PrimaryKey
这个就表示是主键,后面表示该字段是自动生成
@ColumnInfo
就是普通的字段,后面表示字段的名字,不写则默认是属性名称
- 新建Dao
写一个接口类,需要加上@Dao
注解,这里写了CRUD操作,可以看到注解方式和MyBatis几乎是一模一样
package com.example.roomdemo
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
interface UserDao {
@Query("select * from user")
fun getAll() : LiveData>
@Delete
fun deleteUser(user: User)
@Insert
fun insertUser(user: User)
@Insert
fun insertUsers(vararg user: User)
@Update
fun updateUser(user: User)
}
- 新建DataBase
写一个抽象类继承RoomDatabase
,需要加上@DataBase
注解
package com.example.roomdemo
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
/**
* 单例模式
*/
@Database(entities = [User::class], version = 1)
abstract class AppDataBase : RoomDatabase() {
companion object {
private var dataBase: AppDataBase? = null;
@Synchronized
fun getInstance(context: Context): AppDataBase {
if (dataBase == null) {
dataBase = Room.databaseBuilder(context.applicationContext, AppDataBase::class.java, "app_roomdemo")
.build()
}
return dataBase as AppDataBase
}
}
abstract fun userDao() : UserDao
}
@DataBase
中entities
是一个数组,可以传入多个,可以理解成一个数据库中的多张表;version
就是数据库版本,当修改了数据库表结构时就需要修改版本
这是生成DataBase实例的源代码中的方法:
public static RoomDatabase.Builder databaseBuilder(
@NonNull Context context, @NonNull Class klass, @NonNull String name) {
... ...
}
最后一个参数就是数据库的名称
这里使用了单例模式,来确保整个应用中只有一个DataBase
对象,还需要提供一个获取UserDao
的方法
- 新建Repository
为什么要写这个,后面就会知道
在这个类中,我们具体实现了对数据库的CRUD操作
package com.example.roomdemo
import android.content.Context
import android.os.AsyncTask
import androidx.lifecycle.LiveData
class UserRepository(context: Context) {
private var userDao: UserDao? = null
private var userLiveData: LiveData>? = null;
init {
val db: AppDataBase = AppDataBase.getInstance(context.applicationContext)
userDao = db.userDao();
userLiveData = userDao?.getAll()
}
fun getUserLiveData(): LiveData>? {
return userLiveData
}
fun insertUsers(vararg user: User) {
InsertAsyncTask(userDao).execute(*user)
}
fun delUser(user: User) {
DelAsyncTask(userDao).execute(user)
}
fun updateUser(user: User) {
UpdateAsyncTask(userDao).execute(user)
}
class InsertAsyncTask(userDao: UserDao?) : AsyncTask() {
private var userDao: UserDao? = null
init {
this.userDao = userDao
}
override fun doInBackground(vararg params: User): Void? {
userDao?.insertUsers(*params)
return null
}
}
class DelAsyncTask(userDao: UserDao?) : AsyncTask() {
private var userDao: UserDao? = null
init {
this.userDao = userDao
}
override fun doInBackground(vararg params: User): Void? {
userDao?.deleteUser(params[0])
return null
}
}
class UpdateAsyncTask(userDao: UserDao?) : AsyncTask() {
private var userDao: UserDao? = null
init {
this.userDao = userDao
}
override fun doInBackground(vararg params: User): Void? {
userDao?.updateUser(params[0])
return null
}
}
}
因为数据库操作比较耗时所以是不能放在主线程中的,所以需要使用子线程AsyncTask
,当然也可以修改获得DataBase实例部分的代码,加上.allowMainThreadQueries()
,表示允许在主线程中运行:
dataBase = Room.databaseBuilder(context.applicationContext, AppDataBase::class.java, "app_roomdemo")
.allowMainThreadQueries()
.build()
- 新建ViewModel
通过前面我们了解到ViwModel的作用就是管理UI的数据,所以我们在ViewModel中封装好方法,这样Activity/Fragment中只需要调用Viewmodel中的方法即可
package com.example.roomdemo
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
class UserViewModel(application: Application) : AndroidViewModel(application) {
private var userRepository: UserRepository? = null
init {
userRepository = UserRepository(application)
}
fun getUserLiveData(): LiveData>? {
return userRepository?.getUserLiveData()
}
fun insertUsers(vararg user: User) {
userRepository?.insertUsers(*user)
}
fun delUser(user: User) {
userRepository?.delUser(user)
}
fun updateUser(user: User) {
userRepository?.updateUser(user)
}
}
可以看到ViewModel通过UserRepository来获得数据,如果没有这个Repository,那么对数据库的操作就要全写在ViewModel中,这样不符合ViewModel的定义,ViewModel本来就是用于管理数据,这样分离开能够使代码更具有可读性
- 使用RecyclerView来显示数据
在使用RecyclerView之前需要写一个适配器:
package com.example.roomdemo
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class RecyclerViewAdapter : RecyclerView.Adapter() {
private var userList: List = ArrayList()
fun setUserList(userList: List) {
this.userList = userList
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
var view = layoutInflater.inflate(R.layout.cell_normal, parent, false)
return MyViewHolder(view)
}
override fun getItemCount(): Int {
return userList.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val user = userList[position]
holder.numberView?.text = (position + 1).toString()
holder.nameView?.text = user.name
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var numberView: TextView? = null;
var nameView: TextView? = null
init {
numberView = itemView.findViewById(R.id.numView)
nameView = itemView.findViewById(R.id.nameView)
}
}
}
- 修改MainActivity'
就是RecyclerView的初始化以及按钮的监听,当数据发生改变时,通知其刷新视图
package com.example.roomdemo;
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity(), View.OnClickListener {
var db: AppDataBase? = null
var addBtn: Button? = null
var delBtn: Button? = null
var updateBtn: Button? = null
var viewModel: UserViewModel? = null
var recyclerView: RecyclerView? = null
var adapter: RecyclerViewAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addBtn = findViewById(R.id.add_user)
delBtn = findViewById(R.id.del)
updateBtn = findViewById(R.id.update)
recyclerView = findViewById(R.id.recyclerView)
adapter = RecyclerViewAdapter()
recyclerView?.layoutManager = LinearLayoutManager(this)
recyclerView?.adapter = adapter
viewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
viewModel!!.getUserLiveData()!!.observe(this, Observer { userList ->
adapter?.setUserList(userList)
adapter?.notifyDataSetChanged()
})
addBtn?.setOnClickListener(this)
delBtn?.setOnClickListener(this)
updateBtn?.setOnClickListener(this)
}
override fun onClick(v: View?) {
print("dianji")
when (v?.id) {
R.id.add_user -> {
val user1 = User("hhh")
val user2 = User("eee")
viewModel?.insertUsers(user1, user2)
}
R.id.del -> {
val user = User(1, "hhh")
viewModel?.delUser(user)
}
R.id.update -> {
val user = User(1, "eee")
viewModel?.updateUser(user)
}
}
}
}
到此就完成了,布局文件我就不贴了,比较多
数据库版本
前面说到当修改了数据库表结构时就需要修改版本,但是仅仅改变版本号是不行的,运行就会报以下错误:
Caused by: java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
看日志可以知道需要添加一个Migration
,修改AppDataBase
package com.example.roomdemo
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
/**
* 单例模式
*/
@Database(entities = [User::class], version = 2)
abstract class AppDataBase : RoomDatabase() {
companion object {
private var dataBase: AppDataBase? = null;
@Synchronized
fun getInstance(context: Context): AppDataBase {
if (dataBase == null) {
dataBase = Room.databaseBuilder(context.applicationContext, AppDataBase::class.java, "app_roomdemo")
.addMigrations(Migration1_2)
.build()
}
return dataBase as AppDataBase
}
val Migration1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER")
}
}
}
abstract fun userDao() : UserDao
}
这样数据库就顺利更新完成了
以上就是当表结构增加字段的操作方法,那么我要删除字段怎么办呢?很遗憾在SQLite中并没有DROP COLUMN
,所以就需要换一种方式:创建一个新表,把数据复制过去,然后删除旧表,再把新表名字改成原来的名字。。。
是不是很麻烦,但是没办法
package com.example.roomdemo
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
/**
* 单例模式
*/
@Database(entities = [User::class], version = 3)
abstract class AppDataBase : RoomDatabase() {
companion object {
private var dataBase: AppDataBase? = null;
@Synchronized
fun getInstance(context: Context): AppDataBase {
if (dataBase == null) {
dataBase = Room.databaseBuilder(context.applicationContext, AppDataBase::class.java, "app_roomdemo")
.addMigrations(migration2_3)
.build()
}
return dataBase as AppDataBase
}
val migration1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER")
}
}
val migration2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE user_temp (id INTEGER PRIMARY KEY NOT NULL, user_name TEXT)")
database.execSQL("INSERT INTO user_temp (id, user_name) SELECT id, user_name FROM user"
)
database.execSQL("DROP TABLE user")
database.execSQL("ALTER TABLE user_temp RENAME TO user")
}
}
}
abstract fun userDao() : UserDao
}
添加一个Migration,可能会出现以下问题:
Caused by: java.lang.IllegalStateException: Migration didn't properly handle: User(com.example.roomdemo.User).
Expected:
TableInfo{name='User', columns={user_name=Column{name='user_name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='User', columns={user_name=Column{name='user_name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
这表明两张表字段不一致,比如一个NULL
,一个是NOT NUL
,修改下即可
如果不出意外的话,数据库表的字段已经修改成功了
修改适配器
当前使用的Adapter实现了默认的RecyclerView.Adapter
,但是我们可以换成更高级的Adapter:ListAdapter
,该适配器也是继承自RecyclerView.Adapter
,他包含了一个List,并且能够计算后台线程List之间的差异
需要修改RecyclerViewAdapter
和MainActivity
,以下是替换后的代码:
package com.example.roomdemo
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Switch
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
class RecyclerViewAdapter : ListAdapter(DIFF_CALLBACK) {
companion object {
val DIFF_CALLBACK = object: DiffUtil.ItemCallback() {
override fun areItemsTheSame(
oldUser: User, newUser: User): Boolean {
return oldUser.id == newUser.id
}
override fun areContentsTheSame(
oldUser: User, newUser: User): Boolean {
return oldUser.name.equals(newUser.name)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
var view = layoutInflater.inflate(R.layout.cell_normal, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val user = getItem(position)
holder.numberView?.text = (position + 1).toString()
holder.nameView?.text = user.name
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var numberView: TextView? = null;
var nameView: TextView? = null
init {
numberView = itemView.findViewById(R.id.numView)
nameView = itemView.findViewById(R.id.nameView)
}
}
}
package com.example.roomdemo;
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.CompoundButton
import android.widget.Switch
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity(), View.OnClickListener {
var addBtn: Button? = null
var delBtn: Button? = null
var updateBtn: Button? = null
var viewModel: UserViewModel? = null
var recyclerView: RecyclerView? = null
var adapter1: RecyclerViewAdapter? = null
var adapter2: RecyclerViewAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addBtn = findViewById(R.id.add_user)
delBtn = findViewById(R.id.del)
updateBtn = findViewById(R.id.update)
recyclerView = findViewById(R.id.recyclerView)
adapter1 = RecyclerViewAdapter()
adapter2 = RecyclerViewAdapter()
recyclerView?.layoutManager = LinearLayoutManager(this)
recyclerView?.adapter = adapter1
viewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
viewModel!!.getUserLiveData()!!.observe(this, Observer { userList ->
adapter1?.submitList(userList)
adapter2?.submitList(userList)
})
addBtn?.setOnClickListener(this)
delBtn?.setOnClickListener(this)
updateBtn?.setOnClickListener(this)
}
override fun onClick(v: View?) {
print("dianji")
when (v?.id) {
R.id.add_user -> {
val user1 = User("hhh")
val user2 = User("eee")
viewModel?.insertUsers(user1, user2)
}
R.id.del -> {
val user = User(1, "hhh")
viewModel?.delUser(user)
}
R.id.update -> {
val user = User(1, "eee")
viewModel?.updateUser(user)
}
}
}
}
这样不仅简化了操作,还自带一个添加删除时的过渡动画