Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份,更是因为其良好的开发体验,大大降低了SQLite的使用门槛。
1. 基本介绍
框架特点
相对于SQLiteOpenHelper
等传统方法,使用Room操作SQLite有以下优势:
- 编译期的SQL语法检查
- 开发高效,避免大量模板代码
- API设计友好,容易理解
- 可以与
RxJava
、LiveData
、Kotlin Coroutines
等进行桥接
添加依赖
dependencies {
implementation "androidx.room:room-runtime:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
}
基本组件
Room的使用,主要涉及以下3个组件
- Database: 访问底层数据库的入口
- Entity: 代表数据库中的表(table),一般用注解
- Data Access Object (DAO): 数据库访问者
这三个组件的概念也出现在其他ORM框架中,有过使用经验的同学理解起来并不困难: 通过Database获取DAO,然后通过DAO查询并获取entities,最终通过entities对数据库table中数据进行读写
Database
Database是我们访问底层数据库的入口,管理着真正的数据库文件。我们使用@Database
定义一个Database类:
- 派生自
RoomDatabase
- 关联其内部数据库table对应的
entities
- 提供获取DAO的抽象方法,且不能有参数
@Database(entities = arrayOf(User::class), version = 1)
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
运行时,我们可以通过Room.databaseBuilder()
或者 Room.inMemoryDatabaseBuilder()
获取Database实例
val db = Room.databaseBuilder(
applicationContext,
UserDatabase::class.java, "users-db"
).build()
创建Databsse的成本较高,推荐使用单例的Database,避免反复创建实例带来的开销
Entity
一个Entity代表数据库中的一张表(table)。我们使用@Entity
定义一个Entiry类,类中的属性对应表中的Column
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
- 所有的属性必须是public、或者有get、set方法
- 属性中至少有一个主键,使用
@PrimaryKey
表示单个主键,也可以像下面这样定义多主键
@Entity(primaryKeys = arrayOf("firstName", "lastName"))
- 当主键值为null时,autoGenerate可以帮助自动生成键值
@PrimaryKey(autoGenerate = true) val uid : Int
- 默认情况下使用类名作为数据库table名,也可使用
tableName
指定
@Entity(tableName = "users")
- Entity中的所有属性都会被持久化到数据库,除非使用
@Ignore
@Ignore val picture: Bitmap?
- 可以使用
indices
指定数据库索引,unique
设置其为唯一索引
@Entity(indices = arrayOf(Index(value = ["last_name", "address"])))
@Entity(indices = arrayOf(Index(value = ["first_name", "last_name"],
unique = true)))
Data Access Object (DAO)
DAO提供了访问DB的API,我们使用@Dao
定义DAO类,使用@Query
、@Insert
、 @Delete
定义CRUD方法
@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>
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
DAO的方法调用都在当前线程进行,所以要避免在UI线程直接访问
Type Converters
有时,需要将自定义类型的数据持久化到DB,此时需要借助Converters
进行转换
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time?.toLong()
}
}
在声明Database时,指定此Converters
@Database(entities = arrayOf(User::class), version = 1)
@TypeConverters(Converters::class)
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
2. Data Access Objects(DAO)
Room中使用Data Access Objects(DAO)对数据库进行读写,相对于SQL语句直接查询,DAO可以定义更加友好的API。DAO中可以自定义CURD方法,还可以方便地与RxJava
、LiveData
等进行集成。
我们可以使用接口或者抽象类定一个DAO,如果使用抽象类,可以选择性的为其定义构造函数,并接受Database作为唯一参数。
Room在编译期会基于定义的DAO生成具体实现类,实现具体CURD方法。
@Insert 插入
@Insert
注解插入操作,编译期生成的代码会将所有的参数以单独的事务更新到DB。
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
@Insert fun insertBothUsers(user1: User, user2: User)
@Insert fun insertUsersAndFriends(user: User, friends: List<User>)
}
onConflict
设置当事务中遇到冲突时的策略
- OnConflictStrategy.REPLACE : 替换旧值,继续当前事务
- OnConflictStrategy.ROLLBACK : 回滚当前事务
- OnConflictStrategy.ABORT : 结束当前事务、回滚
- OnConflictStrategy.FAIL : 当前事务失败、回滚
- OnConflictStrategy.NONE : 忽略冲突,继续当前事务
最新代码中ROLLBACK 和 FAIL 已经deprecated了,使用ABORT替代
@Update 更新
@Update注解定义更新操作,根据参数对象的主键更新指定row的数据
@Dao
interface UserDao {
@Update(onConflict = OnConflictStrategy.REPLACE)
fun updateUsers(vararg users: User)
@Update fun update(user: User)
}
@Delete 删除
@Delete定义删除操作,根据主键删除指定row
@Dao
interface UserDao {
@Delete
fun deleteUsers(vararg users: User)
}
@Query 查询
@Query
注解定义查询操作。@Query中的SQL语句以及返回值类型等会在编译期进行检查,更早的暴露问题
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun loadAllUsers(): Array<User>
}
指定参数
可以用参数指定@Query中的where条件:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>
@Query("SELECT * FROM users WHERE first_name LIKE :search " +
"OR last_name LIKE :search")
fun findUserWithName(search: String): List<User>
}
返回子集
返回的结果可以是所有column的子集:
data class NameTuple(
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)@Dao
interface UserDao {
@Query("SELECT first_name, last_name FROM users")
fun loadFullName(): List<NameTuple>
}
返回Cursor
返回Cursor,可以基于Cursor进行进一步操作
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun loadAllUsers(): Cursor
}
多表查询
@Dao
interface BookDao {
@Query(
"SELECT * FROM book " +
"INNER JOIN loan ON loan.book_id = book.id " +
"INNER JOIN user ON user.id = loan.user_id " +
"WHERE users.name LIKE :userName"
)
fun findBooksBorrowedByNameSync(userName: String): List<Book>
}
SQL可以写任何语句,包括多表连接等
返回类型
Room可以返回Coroutine、RxJava等多个常用库的类型结果,便于在异步、响应式开发中使用
3.实体与数据表关系
对于关系型数据库来说,最重要的是如何将数据拆分为有相关关系的多个数据表。SQLite作为关系型数据库,允许entits之间可以有多种关系,Room提供了多种方式表达这种关系。
@Embedded内嵌对象
@Embedded
注解可以将一个Entity作为属性内嵌到另一Entity,我们可以像访问Column一样访问内嵌Entity
内嵌实体本身也可以包括其他内嵌对象
data class Address(
val street: String?,
val state: String?,
val city: String?,
val postCode: Int
)
@Entity
data class User(
@PrimaryKey val id: Int,
val firstName: String?,
@Embedded val address: Address?
)
如上,等价于User表包含了 id, firstName, street, state, city, postCode
等column
如果内嵌对象中存在同名字段,可以使用prefix指定前缀加以区分
@Embedded通过把内嵌对象的属性解包到被宿主中,建立了实体的连接。此外还可以通过@Relation
和 foreignkeys
来描述实体之间更加复杂的关系。
我们至少可以描述三种实体关系
- 一对一
- 一对多或多对一
- 多对多
一对一
主表(Parent Entity)中的每条记录与从表(Child Entity)中的每条记录一一对应。
设想一个音乐app的场景,用户(User)和曲库(Library)有如下关系:
- 一个User只有一个Library
- 一个Library只属于唯一User
@Entity
data class User(
@PrimaryKey val userId: Long,
val name: String,
val age: Int
)
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "userId",
childColumns = "userOwnerId",
onDelete = CASCADE))
data class Library(
@PrimaryKey val libraryId: Long,
val title: String,
val userOwnerId: Long
)
data class UserAndLibrary(
@Embedded val user: User,
@Relation(
parentColumn = "userId",
entityColumn = "userOwnerId"
)
val library: Library
)
如上,User和Library之间属于一对一的关系。
foreignkeys
foreignkeys
作为@Relation的属性用来定义外键约束。外键只能在从表上,从表需要有字段对应到主表的主键(Library的userOwnerId对应到User的userId)。
外键约束属性:当有删除或者更新操作的时候发出这个约束
通过外键约束
,对主表的操作会受到从表的影响。例如当在主表(即外键的来源表)中删除对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除。
@Relation
为了能够对User以及关联的Library进行查询,需要为两者之间建立一对一关系:
- 通过
UserAndLibrary
定义这种关系,包含两个成员分别是主表和从表的实体 - 为从表添加
@Relation
注解 parentColumn
:主表主键entityColumn
:从表外键约束的字段
然后,可以通过UserAndLibrary进行查询
@Transaction
@Query("SELECT * FROM User")
fun getUsersAndLibraries(): List<UserAndLibrary>
此方法要从两个表中分别进行两次查询,所以
@Transaction
确保方法中的多次查询的原子性
一对多
主表中的一条记录对应从表中的零到多条记录。
在前面音乐APP的例子中,有如下一对多关系:
- 一个User可以创建多个播放列表(Playlist)
- 每个Playlist只能有唯一的创作者(User)
@Entity
data class User(
@PrimaryKey val userId: Long,
val name: String,
val age: Int
)
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "userId",
childColumns = "userCreatorId",
onDelete = CASCADE))
data class Playlist(
@PrimaryKey val playlistId: Long,
val userCreatorId: Long,
val playlistName: String
)
data class UserWithPlaylists(
@Embedded val user: User,
@Relation(
parentColumn = "userId",
entityColumn = "userCreatorId"
)
val playlists: List<Playlist>
)
可以看到,一对多关系的UserWithPlaylists
与一对一类似, 只是playlists
需要是一个List
表示从表中的记录不止一个。
查询方法如下:
@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylists(): List<UserWithPlaylists>
多对多
主表中的一条记录对应从表中的零活多个,反之亦然:
- 每个Playlist中可以有很多首歌曲(Song)
- 每个Song可以归属不同的Playlist
因此,Playlist与Song之间是多对多的关系
@Entity
data class Playlist(
@PrimaryKey val id: Long,
val playlistName: String
)
@Entity
data class Song(
@PrimaryKey val id: Long,
val songName: String,
val artist: String
)
@Entity(primaryKeys = ["playlistId", "songId"],
foreignKeys = {
@ForeignKey(entity = Playlist.class,
parentColumns = "id",
childColumns = "playlistId"),
@ForeignKey(entity = Song.class,
parentColumns = "id",
childColumns = "songId")
}))
data class PlaylistSongCrossRef(
val playlistId: Long,
val songId: Long
)
多对多关系中,Song和Playlist之间没有明确的外键约束关系,需要定义一个 associative entity
(又或者称作交叉连接表):PlaylistSongCrossRef
,然后分别与Song和Playlist建立外键约束。交叉连接的结果是Song与Playlist的笛卡尔积,即两个表中所有记录的组合。
基于交叉连接表,我们可以获取一首Song与其包含它的所有Playlist,又或者一个Playlist与其包含的所有Song。
如果使用SQL获取指定Playlist与其包含的Song,需要两条查询:
# 查询playlist信息
SELECT * FROM Playlist
# 查询Song信息
SELECT
Song.id AS songId,
Song.name AS songName,
_junction.playlistId
FROM
PlaylistSongCrossRef AS _junction
INNER JOIN Song ON (_junction.songId = Song.id)
# WHERE _junction.playlistId IN (playlistId1, playlistId2, …)
如果使用Room,则需要定义PlaylistWithSongs类,并告诉其使用PlaylistSongCrossRef作为连接:
data class PlaylistWithSongs(
@Embedded val playlist: Playlist,
@Relation(
parentColumn = "playlistId",
entityColumn = "songId",
associateBy = @Junction(PlaylistSongCrossRef::class)
)
val songs: List<Song>
)
同理,也可定义SongWithPlaylists
data class SongWithPlaylists(
@Embedded val song: Song,
@Relation(
parentColumn = "songId",
entityColumn = "playlistId",
associateBy = @Junction(PlaylistSongCrossRef::class)
)
val playlists: List<Playlist>
)
查询与前面类似,很简单:
@Transaction
@Query("SELECT * FROM Playlist")
fun getPlaylistsWithSongs(): List<PlaylistWithSongs>
@Transaction
@Query("SELECT * FROM Song")
fun getSongsWithPlaylists(): List<SongWithPlaylists>
4. 实现原理
通过例子了解一下Room的底层实现原理。
Database
定义一个UserDatabase,只有一个实体User:
@Database(entities = [User::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
Entity
User有三个字段(Column):
@Entity(tableName = USERS_TABLE)
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = FIRST_NAME_COLUMN) val firstName: String?,
@ColumnInfo(name = LAST_NAME_COLUMN) val lastName: String?
)
UserDao
通过接口定义UserDao
@Dao
interface UserDao {
@Query("SELECT * FROM $USERS_TABLE")
fun getAll(): List<User>
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
源码分析
Room在编译期通过kapt处理@Dao和@Database注解,并生成DAO和Database的实现类,UserDatabase_Impl
和UserDao_Impl
。kapt生成的代码在 build/generated/source/kapt/
UserDatabase_Impl
public final class UserDatabase_Impl extends UserDatabase {
private volatile UserDao _userDao;
//RoomDataBase的init中调用
@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
@Override
public void createAllTables(SupportSQLiteDatabase _db) {
//Implementation
}
@Override
protected void onCreate(SupportSQLiteDatabase _db) {
//Implementation
}
});
final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
.name(configuration.name)
.callback(_openCallback)
.build();
final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
return _helper;
}
@Override
protected InvalidationTracker createInvalidationTracker() {
final HashMap<String, String> _shadowTablesMap = new HashMap<String, String>(0);
HashMap<String, Set<String>> _viewTables = new HashMap<String, Set<String>>(0);
return new InvalidationTracker(this, _shadowTablesMap, _viewTables, "users");
}
@Override
public void clearAllTables() {
super.assertNotMainThread();
final SupportSQLiteDatabase _db = super.getOpenHelper().getWritableDatabase();
try {
super.beginTransaction();
_db.execSQL("DELETE FROM `users`");
super.setTransactionSuccessful();
} finally {
super.endTransaction();
_db.query("PRAGMA wal_checkpoint(FULL)").close();
if (!_db.inTransaction()) {
_db.execSQL("VACUUM");
}
}
}
@Override
public UserDao userDao() {
//实现见后文
}
- createOpenHelper:
Room.databaseBuilder().build()
创建Database时,会调用实现类的createOpenHelper()创建SupportSQLiteOpenHelper
,此Helper用来创建DB以及管理版本 - createInvalidationTracker :创建跟踪器,确保table的记录修改时能通知到相关回调方
- clearAllTables:清空table的实现
- userDao:创建
UserDao_Impl
UserDao_Impl
public final class UserDao_Impl implements UserDao {
private final RoomDatabase __db;
private final EntityInsertionAdapter<User> __insertionAdapterOfUser;
private final EntityDeletionOrUpdateAdapter<User> __deletionAdapterOfUser;
public UserDao_Impl(RoomDatabase __db) {
this.__db = __db;
this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
//Implementation
};
this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
//Implementation
};
}
@Override
public void insertAll(final User... users) {
//Implementation
}
@Override
public void delete(final User user) {
//Implementation
}
@Override
public List<User> getAll() {
//Implementation
}
@Override
public List<User> loadAllByIds(final int[] userIds) {
//Implementation
}
@Override
public User findByName(final String first, final String last) {
//Implementation
}
}
UserDao_Impl 主要有三个属性:
- __db:RoomDatabase的实例
- __insertionAdapterOfUser :
EntityInsertionAdapterd
实例,用于数据insert。上例中,将在installAll()
中调用 - __deletionAdapterOfUser:
EntityDeletionOrUpdateAdapter
实例,用于数据的update/delete。 上例中,在delete()
中调用
RoomDatabase.Builder
Room通过Build模式创建Database实例
val userDatabase = Room.databaseBuilder(
applicationContext,
UserDatabase::class.java,
"users-db"
).build()
Builder的好处时便于对Database进行配置
- createFromAsset()/createFromFile() :从SD卡或者Asset的db文件创建RoomDatabase实例
- addMigrations() :添加一个数据库迁移(migration),当进行数据版本升级时需要
- allowMainThreadQueries() :允许在UI线程进行数据库查询,默认是不允许的
- fallbackToDestructiveMigration() :如果找不到migration则重建数据库表(会造成数据丢失)
除上面以外,还有其他很多配置。调用build()
后,创建UserDatabase_Impl,并调用init()
,内部会调用createOpenHelper()
。
userDao()
@Override
public UserDao userDao() {
if (_userDao != null) {
return _userDao;
} else {
synchronized(this) {
if(_userDao == null) {
_userDao = new UserDao_Impl(this);
}
return _userDao;
}
}
}
通过构造参数,向UserDao_Impl传入RoomDatabase
insertAll()
@Override
public void insertAll(final User... users) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
使用__db开启事务,使用__insertionAdapterOfUser执行插入操作
delete()
@Override
public void delete(final User user) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__deletionAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
同insertAll()
getAll()
@Override
public List<User> getAll() {
final String _sql = "SELECT * FROM users";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");
final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "last_name");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item;
final int _tmpUid;
_tmpUid = _cursor.getInt(_cursorIndexOfUid);
final String _tmpFirstName;
_tmpFirstName = _cursor.getString(_cursorIndexOfFirstName);
final String _tmpLastName;
_tmpLastName = _cursor.getString(_cursorIndexOfLastName);
_item = new User(_tmpUid,_tmpFirstName,_tmpLastName);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
基于@Query注解的sql语句创建RoomSQLiteQuery
,然后创建cursor
进行后续操作
5. 数据库升级
当数据库的表结构发生变化时,我们需要通过数据库迁移(Migrations)升级表结构,避免数据丢失。
例如,我们想要为User表增加age字段
| uid | first_name | last_name |
↓↓
| uid | first_name | last_name | age |
数据迁移需要使用Migration类:
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE users ADD COLUMN age INTEGER")
}
}
Migration通过startVersion
和endVersion
表明当前是哪个版本间的迁移,然后在运行时,按照版本顺序调用各Migration,最终迁移到最新的Version
创建Database时设置Migration:
Room.databaseBuilder(
applicationContext,
UserDatabase::class.java,
"users-db"
).addMigrations(MIGRATION_1_2)
.build()
迁移失效
迁移中如果找不到对应版的Migration,会抛出IllegalStateException
:
java.lang.IllegalStateException: A migration from 1 to 2 is necessary.
Please provide a Migration in the builder or call fallbackToDestructiveMigration in the builder in which case Room will re-create all of the tables.
可以添加降级处理,避免crash:
Room.databaseBuilder(
applicationContext,
UserDatabase::class.java,
"users-db"
).fallbackToDestructiveMigration()
.build()
- fallbackToDestructiveMigration:迁移失败时,重建数据库表
- fallbackToDestructiveMigrationFrom:迁移失败时,基于某版本重建数据库表
- fallbackToDestructiveMigrationOnDowngrade:迁移失败,数据库表降级到上一个正常版本
6. 集成三方库(LiveData、RxJava等)
作为Jetpack生态的成员,Room可以很好地兼容Jetpack的其他组件以及ACC推荐的三方库,例如LiveData、RxJava等。
使用LiveData
DAO可以定义LiveData类型的结果,Room内部兼容了LiveData的响应式逻辑。
可观察的查询
通常的Query需要命令式的获取结果,LiveData可以让结果的更新可被观察(Observable Queries)。
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllLiveData(): LiveData<List<User>>
}
当DB的数据发生变化时,Room会更新LiveData:
@Override
public LiveData<List<User>> getAllLiveData() {
final String _sql = "SELECT * FROM users";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return __db.getInvalidationTracker().createLiveData(new String[]{"users"}, false, new Callable<List<User>>() {
@Override
public List<User> call() throws Exception {
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");
final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "last_name");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item;
final int _tmpUid;
_tmpUid = _cursor.getInt(_cursorIndexOfUid);
final String _tmpFirstName;
_tmpFirstName = _cursor.getString(_cursorIndexOfFirstName);
final String _tmpLastName;
_tmpLastName = _cursor.getString(_cursorIndexOfLastName);
_item = new User(_tmpUid,_tmpFirstName,_tmpLastName);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
}
}
@Override
protected void finalize() {
_statement.release();
}
});
}
__db.getInvalidationTracker().createLiveData()
接受3个参数
- tableNames:被观察的表
- inTransaction:查询是否基于事务
- computeFunction:表记录变化时的回调
computeFunction的call中执行真正的sql查询。当Observer首次订阅LiveData时,或者表数据发生变化时,便会执行到这里。
使用RxJava
添加依赖
使用RxJava需要添加以下依赖
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
}
响应式的查询
DAO的返回值类型可以是RxJava2的各种类型:
- @Query注解的方法:返回 Flowable 或 Observable.
- @Insert/@Update/@Delete注解的方法: 返回Completable, Single, and Maybe(Room 2.1.0以上)
@Dao
interface UserDao {
@Query("SELECT * from users where uid = :id LIMIT 1")
fun loadUserById(id: Int): Flowable<User>
@Insert
fun insertUsers(vararg users: User): Completable
@Delete
fun deleteAllUsers(users: List<User>): Single<Int>
}
@Override
public Completable insertLargeNumberOfUsers(final User... users) {
return Completable.fromCallable(new Callable<Void>() {
@Override
public Void call() throws Exception {
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(users);
__db.setTransactionSuccessful();
return null;
} finally {
__db.endTransaction();
}
}
});
}@Override
public Single<Integer> deleteAllUsers(final List<User> users) {
return Single.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int _total = 0;
__db.beginTransaction();
try {
_total +=__deletionAdapterOfUser.handleMultiple(users);
__db.setTransactionSuccessful();
return _total;
} finally {
__db.endTransaction();
}
}
});
}@Override
public Flowable<User> loadUserById(final int id) {
final String _sql = "SELECT * from users where uid = ? LIMIT 1";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, id);
return RxRoom.createFlowable(__db, false, new String[]{"users"}, new Callable<User>() {
@Override
public User call() throws Exception {
//Implementation
}
@Override
protected void finalize() {
_statement.release();
}
});
}
如上,使用fromCallable{...}创建Completable与Single; RxRoom.createFlowable{...}创建Flowable。call()
里执行真正的sql操作
使用协程Coroutine
添加依赖
使用Coroutine需要添加额外依赖:
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" // Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
}
挂起函数定义DAO
为UserDao中的CURD方法添加suspend
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUsers(vararg users: User)
@Update
suspend fun updateUsers(vararg users: User)
@Delete
suspend fun deleteUsers(vararg users: User)
@Query("SELECT * FROM users")
suspend fun loadAllUsers(): Array<User>
}
CoroutinesRoom.execute
中进行真正的sql语句,并通过Continuation将callback变为Coroutine的同步调用
@Override
public Object insertUsers(final User[] users, final Continuation<? super Unit> p1) {
return CoroutinesRoom.execute(__db, true, new Callable<Unit>() {
@Override
public Unit call() throws Exception {
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(users);
__db.setTransactionSuccessful();
return Unit.INSTANCE;
} finally {
__db.endTransaction();
}
}
}, p1);
}
可以对比一下普通版本的insertUsers:
@Override
public void insertUsers(final User... users) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
区别很明显,添加了suspend后,生成代码中会使用CoroutinesRoom.execute
封装协程。