Room

介绍

Room是一个对象关系映射库,拥有SQLite的全部功能,并且能更方便的访问数据库
Room有3个主要组成部分:

  • DataBase:用注解标记一个类为数据库,该类需要继承RoomDataBase,可以通过Room.databaseBuilder获取实例

  • Entity:用注解标记一个类为表,类中的每个属性就是表中的字段

  • Dao:用注解标记一个接口为数据访问对象

这种方式和Java Web中的MyBatis很像

使用

  1. 引入依赖
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了

  1. 新建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就是普通的字段,后面表示字段的名字,不写则默认是属性名称

  1. 新建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)

}
  1. 新建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
}

@DataBaseentities是一个数组,可以传入多个,可以理解成一个数据库中的多张表;version就是数据库版本,当修改了数据库表结构时就需要修改版本

这是生成DataBase实例的源代码中的方法:

public static  RoomDatabase.Builder databaseBuilder(
            @NonNull Context context, @NonNull Class klass, @NonNull String name) {
    ... ...
}

最后一个参数就是数据库的名称

这里使用了单例模式,来确保整个应用中只有一个DataBase对象,还需要提供一个获取UserDao的方法

  1. 新建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()
  1. 新建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本来就是用于管理数据,这样分离开能够使代码更具有可读性

  1. 使用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)
        }
    }
}

  1. 修改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之间的差异

需要修改RecyclerViewAdapterMainActivity,以下是替换后的代码:

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)
            }

        }

    }
}

这样不仅简化了操作,还自带一个添加删除时的过渡动画

你可能感兴趣的:(Room)