Room数据库的使用

废话也很少说了,Room数据库就是对SQLite数据库的封装,使之用起来更方便。Google也说了强烈推荐使用Room来替代SQLite。java


1.在项目中添加Room

dependencies {
	...
    def room_version = "2.1.0-alpha02"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
}

因为使用了Kotlin注解,请确保Gradle里配置了kotlin-kapt插件android

apply plugin: 'kotlin-kapt'

2. 简单使用

Room数据库主要由三个部分组成:web

  • Entity: 数据实体,一个Entity表明一张数据表
  • DAO: 在这里定义数据表的操做方法
  • Database: 数据库

下面详细来说如何实现这三个部分。

数据库

2.1 定义数据表Entity
@Entity(tableName = "user")
data class UserEntity(@PrimaryKey @ColumnInfo(name = "uid") var uid: Int,
                      @ColumnInfo(name = "first_name") var firstName: String?,
                      @ColumnInfo(name = "last_name") var lastName: String?)

要点1:在类上方添加@Entitiy注解,并定义数据表名称
要点2:经过@PrimaryKey来定义主键,@ColumnInfo来定义列名称,也能够不添加注解@ColumnInfo,那么列名称就默认是属性的名称。app

这样就至关于定义了一张表user,user表包含uidfirst_name, last_name三个字段。其中uid为主键,数据类型为Intfirst_namelast_name数据类型为String,且能够为空。

ide

2.2 定义数据表操做方法
@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<UserEntity>

    @Query("SELECT * FROM user WHERE uid = (:userId)")
    fun findById(userId: Int): UserEntity
    
    @Insert
    fun insertAll(vararg userEntities: UserEntity)

    @Delete
    fun delete(vararg user: UserEntity)

    @Update
    fun update(vararg user: UserEntity)

}

能够看到,在这里经过各类注解实现了数据表的各类增删改查方法。其中delete,并非说要传入一个彻底同样的实体,只要PrimaryKey匹配就行。具体就很少讲了,一看就懂。svg

最重要的是要在接口前面添加注解@Dao

ui

2.3 定义数据库Database
@Database(entities = [UserEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase(){

    //获取数据表操做实例
    abstract fun userDao(): UserDao

    //单例模式
    companion object {
        private const val DB_NAME = "app_database"
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase{
            val tempInstance = INSTANCE
            if(tempInstance != null) { return tempInstance }
            synchronized(this){
                val instance = Room.databaseBuilder(context.applicationContext, 
                    AppDatabase::class.java, DB_NAME).build()
                INSTANCE = instance
                return instance
            }
        }
    }
}

直接甩代码了,最重要的固然是要在类前面添加注解@Database,在entities中添加全部数据表的类名,version表示数据库版本,exportSchema表示是否生成数据库结构文件,具体能够参考: 处理Schemathis

数据库初始化方法以下:spa

val db = Room.databaseBuilder(applicationContext,
            AppDatabase::class.java, "database-name").build()

要点1. 自定义的Database类必须继承RoomDatabase。
要点2. 在Database中定义获取数据表操做实例的方法。
要点3. 谷歌建议把Database实例定义成单例模式,由于数据库的初始化消耗量至关大,并且也不必在一个进程中建立多个数据库实例。


##### 2.4操做示例

而后就能够操做数据库了,好比向数据表user插入一行数据,以下:

val user = UserEntity(1, "rx", "chen")
AppDatabase.getDatabase(this).userDao().insertAll(user)

插入以后查看user数据表:

在这里插入图片描述

重要提示:Room数据库的操做不能在UI线程执行,请勿在主线程直接调用上述代码


3. 联表查询

除了user表,我还有一张login表,记录用户的登陆次数和最后登陆时间,以下:

@Entity(tableName = "login")
data class LoginEntity(@PrimaryKey @ColumnInfo(name = "id") var uid: Int,
                       @ColumnInfo(name = "login_count") var loginCount: Int,
                       @ColumnInfo(name = "latest_time") var latestTime: String?)

如今我想知道每一个用户的登陆次数,获得每一个用户的名字和登陆次数。

首先,对应咱们须要数据,创建一个相应的数据类,以此来接受获取的数据

class UserLoginCount(var last: String, var first: String, var loginCount: Int) {
    val userName: String
        get() = "$first $last"

    override fun toString(): String {
        return "name: $userName, loginCount: $loginCount"
    }
}

而后建立一个联表查询的DAO,在其中定义好查询方法:

@Dao
interface JoinDao {
    @Query("SELECT user.first_name as first, " +
            "user.last_name as last, " +
            "login.login_count as loginCount " +
            "FROM login, user WHERE user.uid = login.id ")
    fun getUserLoginCount(): List<UserLoginCount>
}

在Database类中定义获取JoinDao的方法:

abstract fun joinDao(): JoinDao

最后就能够直接查询了:

val userLoginCounts = AppDatabase.getDatabase(this).joinDao().getUserLoginCount()

打印出userLoginCounts,结果以下:

name: rx chen, loginCount: 12
name: julia jx, loginCount: 3


4. 数据库升级

如今咱们给user表添加一个字段age,数据库版本从原来的1升到2,Database类修改以下:

@Database(entities = [UserEntity::class, LoginEntity::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase(){

    //获取数据表操做实例
    abstract fun userDao(): UserDao
    abstract fun loginDao(): LoginDao
    abstract fun joinDao(): JoinDao

    //单例数据库
    companion object {

        private const val DB_NAME = "app_database"
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase{
            val tempInstance = INSTANCE
            if(tempInstance != null){
                return tempInstance
            }
            synchronized(this){
                val instance = Room
                    .databaseBuilder(context.applicationContext, 
		                    AppDatabase::class.java, DB_NAME)
                    .fallbackToDestructiveMigration()
                    .addMigrations(MIGRATION_1_2)
                    .build()
                INSTANCE = instance
                return instance
            }
        }

        //升级语句
        private val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
            }
        }

    }

}

同时,别忘了给UserEntity添加age属性

@Entity(tableName = "user")
data class UserEntity(@PrimaryKey @ColumnInfo(name = "uid") var uid: Int,
                      @ColumnInfo(name = "first_name") var firstName: String?,
                      @ColumnInfo(name = "last_name") var lastName: String?,
                      @ColumnInfo(name = "age") var age: Int = 0)

这里最重要的是数据库初始化的时候添加了两个方法:

一个是 fallbackToDestructiveMigration(),这个方法的做用是,当数据库升级没法匹配时,好比执行的升级是从版本2升到版本3,可是却找不到版本2(有可能这时仍然是版本1),此时就会按照最新的版本3重建数据库,那么以前的数据库就会被删除,数据也会被清掉。若是不添加这个方法,遇到这种状况会致使app崩溃。

第二个方法固然是添加升级的方法addMigrations(),这个方法能够接受多个升级语句,好比当前要升级到版本3,但考虑到一部分用户已是版本2,一部分用户仍然在用版本1,那么此时应该提供两种升级方式:从版本1升级到版本3 和 从版本2升级到版本3。写好两种升级语句,一块儿传给addMigrations()方法便可。

执行数据库初始化方法后,数据表user已经添加了age字段,以下:
在这里插入图片描述

PS:数据库升级时写的修改数据表字段或者添加数据表的SQL语句,必定要和Entity定义的内容严格对应,好比我在UserEntity中定义的age字段是非空的,那么SQL语句中就必定要写上age INTEGER NOT NULL,否则就会升级失败甚至致使app崩溃。