http://tommwq.tech/blog/room%...

  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、默认值、列在主键中的顺序。


tommwq
2 声望0 粉丝