Room使用简介

http://tommwq.tech/blog/room%e4%bd%bf%e7%94%a8%e7%ae%80%e4%bb%8b/

  1. Entity
  2. Dao
  3. Database
  4. Room插件
  5. 常见问题
    1. DatabaseBuilder的callback未被调用
    2. Room检查表结构的方法

Room是Jetpack中的ORM组件。Room可以简化SQLite数据库操作。Room包含3个主要的组件:

  • Entity。Entity是实体类,代表数据库里的一张表。
  • DAO。DAO提供了访问数据库的接口,返回Entity或Entity集合。
  • Database。Database是Entity和DAO的集合,代表一个SQLite数据库。Database是我们访问DAO和Entity的入口。

Entity

Entity是实体类,代表一个数据表。我们首先看一个简单的例子:

@Entity(tableName="users")
data class User (
    @PrimaryKey
    var uid: Int,
    @ColumnInfo(name = "first_name") 
    var firstName: String?,
    @ColumnInfo(name = "last_name") 
    var lastName: String?
    @Ignore
    var picture: Bitmap?
)
注解 说明
@Entity 声明实体类。
@PrimaryKey 声明主键。
@ColumnInfo 声明字段在数据表中的属性。
@Ignore 禁止将字段映射到数据表。

Room要求实体类必须拥有主键,且主键必须是Int或Long型。@ColumnInfo声明了列名和域名的对照关系。如果列名和域名相同,可以省略这个注解。上面这个Entity对应的SQL模式就是:

CREATE TABLE users (
    INT uid PRIMARY KEY,
    TEXT first_name,
    TEXT last_name
);

可以看到,从Entity到SQL的映射是非常直观的。

实体类的域可以拥有默认值。实体类除了作为数据容器之外,也可以具有行为。参考下面的例子:

@Entity(tableName = "plants")
data class Plant(
    @PrimaryKey @ColumnInfo(name = "id") 
    val plantId: String,
    val name: String,
    val description: String,
    val growZoneNumber: Int,
    val wateringInterval: Int = 7,
    val imageUrl: String = ""
) {
    fun shouldBeWatered(since: Calendar, lastWateringDate: Calendar) =
        since > lastWateringDate.apply { add(DAY_OF_YEAR, wateringInterval) }

    override fun toString() = name
}

Entity告诉Room如何在Java对象和SQL记录之间进行转换。然而要从SQLite数据库中得到Java对象,我们还需要Dao。

Dao

还是从例子入手。

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List<User>

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1")
    fun findByName(first: String, last: String): User

    @Insert
    suspend fun insertAll(vararg users: User)

    @Delete
    suspend fun delete(user: User)
}
注解 说明
@Dao 声明接口是DAO。
@Query 将SQL查询语句映射为Java方法。
@Insert 将SQL插入语句映射为Java方法。
@Delete 将SQL删除语句映射为Java方法。

从例子里可以看出,DAO和Entity有两个区别,首先Entity是类,而DAO是接口。其次,Entity将Java对象映射为SQL记录,将域映射为数据表中的列;DAO将SQL语句映射为Java方法。我们不需要手动编写这些方法,Room会自动生成它们。

数据库查询会引发磁盘IO,这是一个耗时操作。为了避免ANR,需要将数据库查询放到后台线程里执行。很多时候我们需要根据查询结果来更新界面,而界面必须在主线程中修改。那么如何将后台线程查询出的数据传递给主线程呢?你可以自己编写Handler,更简单的办法是让查询方法返回LiveData。

@Dao
interface PlantDao {
    @Query("SELECT * FROM plants ORDER BY name")
    fun getPlants(): LiveData<List<Plant>>

    @Query("SELECT * FROM plants WHERE growZoneNumber = :growZoneNumber ORDER BY name")
    fun getPlantsWithGrowZoneNumber(growZoneNumber: Int): LiveData<List<Plant>>

    @Query("SELECT * FROM plants WHERE id = :plantId")
    fun getPlant(plantId: String): LiveData<Plant>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(plants: List<Plant>)
}

这里简单介绍一下LiveData。LiveData是一个为更新界面而定制的Observable,它将后台线程的数据投递到主线程。为了避免过度渲染,LiveData只在Activity或Fragment活跃的时候才投递数据。

如果使用kotlin进行开发,可以将DAO方法声明为suspend,配合viewModelScope使用。

Database

Database是Entity和DAO的集合,也是访问Entity和DAO的入口。Database是一个抽象类,每个DAO由一个抽闲方法返回。

@Database(entities = [User::class], version = 1)
abstract class AppDatabase: RoomDatabase() {
    abstract fun userDao(): UserDao
}

此外Entity必须在@Database中进行注册。

实际的Database类也是由Room生成的。通过Room.databaseBuilder可以构造Database类。

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

综合起来,Room的用法可以总结为:

  • 用Entity封装数据记录,用DAO映射查询语句。
  • 通过databaseBuilder得到Database,通过Database得到DAO,通过DAO管理Entity。

Room插件

Room会自动生成类,这个动作是在编译期完成的,因为我们需要引入编译插件。

apply plugin: 'kotlin-kapt'

dependencies {
    def room_version = "2.1.0-alpha04"
    kapt "android.arch.persistence.room:compiler:$room_version"
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"

   implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.0-alpha'
   implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.0-alpha'

   implementation 'androidx.room:room-runtime:2.1.0-alpha06'
   kapt 'androidx.room:room-compiler:2.1.0-alpha06'
   implementation 'androidx.room:room-ktx:2.1.0-alpha06'
}

常见问题

DatabaseBuilder的callback未被调用

Room底层使用了SQLiteOpenHelper,只有当数据库被实际使用时,数据库才会被建立,回调函数才被调用。如果要手动调用callback,可以执行

// and then
db.beginTransaction()
db.endTransaction()

// or query a dummy select statement
db.query("select 1", null)
return db

Room检查表结构的方法

Room的createFromAsset使用PRAGMA tableinfo('tbl')来得到表的结构,并生成TableInfo实例。将这个实例和由Entity类生成的TableInfo进行比对,如果不一致,抛出IllegalStateException异常。

"Migration didn't properly handle XXX

tableinfo为每个列生成一行,记录了列的编号、名字、数据类型、是否可为NULL、默认值、列在主键中的顺序。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342