【Android Jetpack】Room数据库的使用及原理详解

简介: Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份,更是因为其良

Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份,更是因为其良好的开发体验,大大降低了SQLite的使用门槛。

1. 基本介绍

框架特点

相对于SQLiteOpenHelper等传统方法,使用Room操作SQLite有以下优势:

  • 编译期的SQL语法检查
  • 开发高效,避免大量模板代码
  • API设计友好,容易理解
  • 可以与RxJavaLiveDataKotlin 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方法,还可以方便地与RxJavaLiveData等进行集成。

我们可以使用接口或者抽象类定一个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通过把内嵌对象的属性解包到被宿主中,建立了实体的连接。此外还可以通过@Relationforeignkeys来描述实体之间更加复杂的关系。

我们至少可以描述三种实体关系

  • 一对一
  • 一对多或多对一
  • 多对多

一对一

主表(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_ImplUserDao_Impl。kapt生成的代码在 build/generated/source/kapt/

133f44c167534fb49e6aaf0df1148800~tplv-k3u1fbpfcp-zoom-1.image

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() {
    //实现见后文
}
  • createOpenHelperRoom.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的实例
  • __insertionAdapterOfUserEntityInsertionAdapterd实例,用于数据insert。上例中,将在installAll()中调用
  • __deletionAdapterOfUserEntityDeletionOrUpdateAdapter实例,用于数据的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通过startVersionendVersion表明当前是哪个版本间的迁移,然后在运行时,按照版本顺序调用各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封装协程。

目录
相关文章
|
2月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
155 8
|
20天前
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
48 8
|
1月前
|
缓存 算法 关系型数据库
Mysql(3)—数据库相关概念及工作原理
数据库是一个以某种有组织的方式存储的数据集合。它通常包括一个或多个不同的主题领域或用途的数据表。
50 5
Mysql(3)—数据库相关概念及工作原理
|
16天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
34 2
|
1月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
35 3
|
1月前
|
SQL 关系型数据库 数据库
SQL数据库:核心原理与应用实践
随着信息技术的飞速发展,数据库管理系统已成为各类组织和企业中不可或缺的核心组件。在众多数据库管理系统中,SQL(结构化查询语言)数据库以其强大的数据管理能力和灵活性,广泛应用于各类业务场景。本文将深入探讨SQL数据库的基本原理、核心特性以及实际应用。一、SQL数据库概述SQL数据库是一种关系型数据库
51 5
|
1月前
|
SQL 关系型数据库 MySQL
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
|
2月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
45 2
|
2月前
|
编解码 前端开发 Android开发
Android经典实战之TextureView原理和高级用法
本文介绍了 `TextureView` 的原理和特点,包括其硬件加速渲染的优势及与其他视图叠加使用的灵活性,并提供了视频播放和自定义绘制的示例代码。通过合理管理生命周期和资源,`TextureView` 可实现高效流畅的图形和视频渲染。
224 12
|
1月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
23 0