1、Room 简介
使用 Jetpack Room
将数据库保存到本地数据库,处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的使用场景是缓存相关的数据,这样一来,当设备无法访问网络时,用户仍然可以在离线状态下浏览该内容。 Room
持久性库在 SQLite
上提供了一个抽象层,以便在充分利用 SQLite
的强大功能的同时,能够流畅地访问数据库。具体来说,Room
具有以下优势:(1)针对 SQL
查询的编译时验证; (2)可最大限度减少重复和容易出错的样板代码的方便注解; (3)简化了数据库迁移路径。
2、Room 依赖
2.1 dependencies 配置
dependencies {
val roomVersion = "2.4.2"
implementation ( "androidx.room:room-runtime:$roomVersion" )
annotationProcessor ( "androidx.room:room-compiler:$roomVersion" )
kapt ( "androidx.room:room-compiler:$roomVersion" )
ksp ( "androidx.room:room-compiler:$roomVersion" )
implementation ( "androidx.room:room-ktx:$roomVersion" )
implementation ( "androidx.room:room-rxjava2:$roomVersion" )
implementation ( "androidx.room:room-rxjava3:$roomVersion" )
implementation ( "androidx.room:room-guava:$roomVersion" )
testImplementation ( "androidx.room:room-testing:$roomVersion" )
implementation ( "androidx.room:room-paging:2.5.0-alpha01" )
}
2.2 gradle 配置
Room
可以在编译时将数据库的架构信息导出为 JSON
文件。
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [
"room.schemaLocation" : "$projectDir/schemas" . toString ( ) ,
"room.incremental" : "true" ,
"room.expandProjection" : "true" ]
}
}
}
}
room.schemaLocation
: 输出数据库概要, 可以查看字段信息, 版本号, 数据库创建语句等room.incremental
: 启用 Gradle
增量注释处理器room.expandProjection
: 在使用星投影时会根据函数返回类型来重写 SQL
查询语句
3、Room 简单使用
3.1 Room 主要组件
Room
包含三个主要组件:(1)数据实体,用于表示应用的数据库中的表; (2)数据访问对象 (DAO
),提供您的应用可用于插入、删除、更新和查询数据库中的数据的方法。 (3)数据库类,用于保存数据库并作为应用持久性数据底层连接的主要访问点;
3.2 数据实体
定义数据实体,用于表示应用的数据库中的表(参考:Room 实体定义数据更详细使用文献 )。 举例:以下代码定义了一个 User
数据实体。User
的每个实例都代表应用数据库中 user
表中的一行。
@Entity ( tableName = "user" )
data class UserEntity (
@PrimaryKey var uid: Int,
@ColumnInfo ( name = "sid" ) var sid: Long,
var cid: Long,
var subIndex: Int,
var name: String? ,
var age: Long,
var bool: Boolean
)
3.3 数据访问对象 (DAO)
定义数据访问对象 (DAO
),提供您的应用可用于插入、删除、更新和查询数据库中的数据的方法(参考:Room DAO 访问数据更详细使用文献 )。 举例:以下代码定义了一个名为 UserDao
的 DAO
。UserDao
提供了应用的其余部分用于与 user
表中的数据交互的方法。
@Dao
interface UserDao {
data class UserAttribute ( val uid: Int, val sid: Long, val cid: Long)
@Insert ( onConflict = OnConflictStrategy. REPLACE)
fun insert ( vararg entitys: UserEntity) : List< Long>
@Delete
fun delete ( vararg entitys: UserEntity) : Int
@Query ( "DELETE FROM user" )
fun deleteAll ( ) : Int
@Update
fun update ( vararg entitys: UserEntity) : Int
@Query ( "SELECT * FROM user" )
fun queryAll ( ) : List< UserEntity>
@Update ( entity = UserEntity:: class )
fun updateUserAttribute ( attribute: UserAttribute)
@Query ( "SELECT * FROM user WHERE uid IN (:uids)" )
fun queryWithUids ( uids: IntArray) : List< UserEntity>
@Query ( "SELECT * FROM user WHERE bool = :bool" )
fun queryWithBool ( bool: Boolean) : List< UserEntity>
@Query ( "SELECT * FROM user WHERE bool = 1" )
fun queryWithBoolTure ( bool: Boolean) : List< UserEntity>
@Query ( "SELECT * FROM user WHERE name LIKE '%'||:user||'%' LIMIT 1" )
fun queryWithFirstNameAndLastName ( user: String) : UserEntity?
@Query ( "SELECT count(*) FROM user" )
fun queryAllCount ( ) : Long
@Query ( "SELECT MAX(subIndex) FROM user WHERE sid = :sid AND cid = :cid" )
fun queryMaxClassIndex ( sid: Long, cid: Long) : Long
@Query ( "SELECT * FROM user WHERE age > :minAge" )
fun queryOlderThanAge ( minAge: Int) : Array< UserEntity>
@Query ( "SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge" )
fun queryBetweenAge ( minAge: Int, maxAge: Int) : MutableList< UserEntity>
}
3.4 数据库类
定义数据库配置,并作为应用对持久性数据的主要访问点。 数据库类必须满足以下条件: (1)该类必须带有 @Database
注解,该注解包含列出所有与数据库关联的数据实体的 entities
数组; (2)该类必须是一个抽象类,用于扩展 RoomDatabase
; (3)对于与数据库关联的每个 DAO
类,数据库类必须定义一个具有零参数的抽象方法,并返回 DAO
类的实例。 注意: (1)如果您的应用在单个进程中运行,在实例化 AppDatabase
对象时应遵循单例设计模式。每个 RoomDatabase
实例的成本相当高,而您几乎不需要在单个进程中访问多个实例; (2)如果您的应用在多个进程中运行,请在数据库构建器调用中包含 enableMultiInstanceInvalidation()
。这样,如果您在每个进程中都有一个 AppDatabase
实例,可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中 AppDatabase
的实例。
@Database (
entities = [ UserEntity:: class , ] ,
version = 1 ,
exportSchema = true
)
@TypeConverters ( AppDatabaseConverter:: class )
abstract class AppDatabase: RoomDatabase ( ) {
abstract fun userDao ( ) : UserDao
companion object {
private val lock = Any ( )
private val databases = mutableMapOf< Long, AppDatabase> ( )
@Volatile
private var MEMORY_INSTANCE: AppDatabase? = null
private val factory = SupportFactory ( SQLiteDatabase. getBytes ( charArrayOf ( '1' , '2' , '3' , '4' , '5' , '6' ) ) )
@Synchronized
internal fun getDatabase ( loginId: Long = 0 ) : AppDatabase =
databases[ loginId] ?: synchronized ( lock) {
databases[ loginId] ?: buildDatabase ( AppUtils. getAppContext ( ) , loginId) . also { databases[ loginId] = it}
}
@JvmStatic
fun getMemoryDatabase ( context: Context) : AppDatabase =
MEMORY_INSTANCE ?: synchronized ( this ) {
MEMORY_INSTANCE ?: buildDatabase ( context) . also { MEMORY_INSTANCE = it}
}
private fun buildDatabase ( context: Context, loginId: Long = 0 ) =
Room. databaseBuilder (
context. applicationContext, AppDatabase:: class . java, "database_ ${ loginId } .db"
) . openHelperFactory ( factory)
. addCallback ( ClassInDatabaseCallback ( context. applicationContext) )
. allowMainThreadQueries ( )
. build ( )
private fun buildMemoryDatabase ( context: Context) =
Room. inMemoryDatabaseBuilder (
context. applicationContext, AppDatabase:: class . java
) . addCallback ( ClassInDatabaseCallback ( context. applicationContext) )
. build ( )
private class ClassInDatabaseCallback ( private val context: Context) : RoomDatabase. Callback ( ) {
override fun onCreate ( db: SupportSQLiteDatabase) {
super . onCreate ( db)
}
override fun onOpen ( db: SupportSQLiteDatabase) {
super . onOpen ( db)
}
override fun onDestructiveMigration ( db: SupportSQLiteDatabase) {
super . onDestructiveMigration ( db)
}
}
}
@Synchronized
fun cacheDatabase ( block: ( ArrayList< String> ) -> Unit) {
val array = arrayListOf< String> ( )
val dbName = "database_ ${ AccountPreference. getLoginId ( ) } .db"
val dbShmName = "database_ ${ AccountPreference. getLoginId ( ) } .db-shm"
val dbWalName = "database_ ${ AccountPreference. getLoginId ( ) } .db-wal"
if ( AppUtils. getAppContext ( ) . getDatabasePath ( dbName) . exists ( ) ) array. add ( dbName)
if ( AppUtils. getAppContext ( ) . getDatabasePath ( dbShmName) . exists ( ) ) array. add ( dbShmName)
if ( AppUtils. getAppContext ( ) . getDatabasePath ( dbWalName) . exists ( ) ) array. add ( dbWalName)
block ( array)
}
@Synchronized
@Transaction
fun clearDatabase ( ) {
appDatabase ( ) . let {
if ( it. isOpen) {
try {
it. userDao ( ) . deleteAll ( )
} catch ( e: Exception) {
e. printStackTrace ( )
}
}
}
}
}
fun appDatabase ( ) : AppDatabase = AppDatabase. getDatabase ( AccountPreference. getLoginId ( ) )
3.5 调用
val userDao = appDatabase ( ) . userDao ( )
val users: List< UserEntity> = userDao. queryAll ( )
4、Room 的 7 个高级技巧
4.1 预填充数据库
您是否需要在数据库创建后或打开数据库时将默认数据添加到数据库中? 使用 RoomDatabase#addCallback()
在构建 RoomDatabase
时调用该方法并覆盖 onCreate()
或 onOpen()
方法。 onCreate()
将在第一次创建数据库时调用,在创建表之后。onOpen()
在打开数据库时调用。由于只有在这些方法返回后才能访问 DAO
,所以我们正在创建一个新线程,在其中我们获取对数据库的引用,获取 DAO
,然后插入数据。注意:使用该 ioThread
方法时,如果您的应用程序在第一次启动时崩溃,在数据库创建和插入之间,数据将永远不会被插入。
Room. databaseBuilder ( context. applicationContext , DataDatabase:: class . java, "Sample.db" )
. addCallback ( object : Callback ( ) {
override fun onCreate ( db: SupportSQLiteDatabase) {
super . onCreate ( db)
ioThread {
getInstance ( context) . dataDao ( ) . insert ( PREPOPULATE_DATA)
}
}
} )
. build ( )
4.2 使用 DAO 的继承能力
您的数据库中是否有多个表并且发现自己在复制相同的 Insert
、Update
和 Delete
方法?DAO
支持继承,因此创建一个类,并在那里 BaseDao<T>
定义您的泛型 @Insert
和方法。让每个 DAO
扩展并添加特定于它们的方法。 注意:DAO
必须是接口或抽象类,因为 Room
在编译时生成它们的实现,包括来自 BaseDao
。
abstract class BaseDao< T> {
val tableName: String
get ( ) {
return try {
val clazz = ( javaClass. superclass. genericSuperclass as ParameterizedType) . actualTypeArguments[ 0 ] as Class< * >
AppDatabaseConstant. simpleNameConvertTabName ( clazz. simpleName)
} catch ( e: Exception) {
e. printStackTrace ( )
""
}
}
@RawQuery
abstract fun doRawQueryReturnInt ( query: SupportSQLiteQuery) : Int
@RawQuery
abstract fun doRawQueryReturnList ( query: SupportSQLiteQuery) : List< T>
@Insert ( onConflict = OnConflictStrategy. REPLACE)
abstract fun insert ( vararg entitys: T) : List< Long>
@Delete
abstract fun delete ( vararg entitys: T) : Int
fun deleteAll ( ) : Int {
val query = SimpleSQLiteQuery ( "DELETE FROM $ tableName " )
return doRawQueryReturnInt ( query)
}
@Update
abstract fun update ( vararg entitys: T) : Int
fun queryAll ( ) : List< T> {
val query = SimpleSQLiteQuery ( "SELECT * FROM $ tableName " )
return doRawQueryReturnList ( query) ?: emptyList ( )
}
}
@Dao
abstract class UserDao : BaseDao< UserEntity> ( ) {
@Query ( "SELECT * FROM user WHERE uid IN (:uids)" )
fun queryWithUids ( uids: IntArray) : List< UserEntity>
}
4.3 用最少的样板代码在事务中执行查询
注释方法 @Transaction
确保您在该方法中执行的所有数据库操作都将在一个事务中运行,当方法体中抛出异常时,事务将失败。
@Dao
abstract class UserDao {
@Transaction
open fun updateData ( users: List< UserEntity> ) {
deleteAllUsers ( )
insertAll ( users)
}
@Insert
abstract fun insertAll ( users: List< UserEntity> )
@Query ( "DELETE FROM Users" )
abstract fun deleteAllUsers ( )
}
4.4 只读你需要的
查询数据库时,是否使用了查询中返回的所有字段?注意您的应用程序使用的内存量,并仅加载您最终将使用的字段子集。这也将通过降低 IO
成本来提高查询速度。Room
将为您完成列和对象之间的映射。 考虑这个复杂的 User
对象:
@Entity ( tableName = "user" )
data class UserEntity (
@PrimaryKey var uid: Int,
@ColumnInfo ( name = "sid" ) var sid: Long,
var cid: Long,
var subIndex: Int,
var name: String,
var age: Long,
var bool: Boolean
)
在某些屏幕上,我们不需要显示所有这些信息。因此,我们可以创建一个 UserMiniEntity
只保存所需数据的对象。
data class UserMiniEntity (
var uid: Int,
var name: String,
var age: Long
)
在 DAO
类中,我们定义查询并从 user
表中选择正确的列。
@Dao
interface UserDao {
@Query ( "SELECT uid, name, age FROM user" )
fun queryUserMiniEntity ( ) : List< UserMiniEntity>
}
4.5 在具有外键的实体之间实施约束
尽管 Room
不直接支持关系,但它允许您定义实体之间的外键约束。 Room
具有 @ForeignKey
注释,这是 @Entity
注释的一部分,以允许使用 SQLite
外键功能。它在表之间强制实施约束,以确保在您修改数据库时关系有效。在实体上,定义要引用的父实体、其中的列以及当前实体中的列。考虑一个 UserEntity
和一个 PetEntity
类。 PetEntity
有一个 wid
,它是作为外键引用的用户 uid
。
@Entity (
tableName = "pet" ,
foreignKeys = [ ForeignKey ( entity = UserEntity:: class , parentColumns = arrayOf ( "uid" ) , childColumns = arrayOf ( "wid" ) ) ]
)
data class PetPetEntity (
@PrimaryKey var petId: String,
var wid: Long,
var name: String
)
或者,您可以定义在数据库中删除或更新父实体时要执行的操作。您可以选择以下之一:NO_ACTION
、RESTRICT
、SET_NULL
、SET_DEFAULT
或 CASCADE
,它们的行为与 SQLite
中的相同。 注意:在 Room
中,SET_DEFAULT
用作 SET_NULL
,因为 Room
还不允许为列设置默认值。
4.6 通过简化一对多查询 @Relation
在 4.5
的示例中,我们可以说我们有一个一对多的关系:一个用户可以拥有多个宠物。假设我们想要获取带有宠物的用户列表:List<UserAndAllPets>
。
data class UserAndAllPets (
var user: UserEntity,
var pets: List< PetPetEntity> = ArrayList ( )
)
要手动执行此操作,我们需要实现 2 个查询:一个获取所有用户的列表,另一个获取基于用户 uid
的宠物列表。
@Query ( "SELECT * FROM user" )
fun queryUsers ( ) : List< UserEntity>
@Query ( "SELECT * FROM pet WHERE wid = :uid" )
fun queryPetsForUser ( uid: Int) : List< PetPetEntity>
然后我们将遍历用户列表并查询 pet
表。为了简化这一点,Room
的 @Relation
注解会自动获取相关实体。 @Relation
只能应用于 List
或 Set
对象。 UserAndAllPets
类必须更新:
class UserAndAllPets {
@Embedded
var user: UserEntity? = null
@Relation ( parentColumn = "uid" , entityColumn = "wid" )
var pets: List< PetPetEntity> = ArrayList ( )
}
在 DAO
中,我们定义了一个查询,Room
将同时查询 user
和 pet
表并处理对象映射。
@Transaction
@Query ( "SELECT * FROM user" )
fun queryUserAndAllPets ( ) : List< UserAndAllPets>
4.7 避免可观察查询的误报通知
假设您想根据可观察查询中的用户 uid
获取用户:
@Query ( "SELECT * FROM user WHERE uid = :uid" )
fun getUserByUid ( uid: Int) : LiveData< UserEntity>
@Query ( "SELECT * FROM user WHERE uid = :uid" )
fun getUserByUid ( uid: Int) : Flowable< UserEntity>
UserEntity
每当该用户更新时,您都会获得该对象的新发射。UserEntity
但是当表上发生与您感兴趣的无关的其他更改(删除、更新或插入)时,您也会得到相同的对象 UserEntity
,从而导致误报通知。更重要的是,如果您的查询涉及多个表,那么只要其中任何一个发生更改,您就会得到一个新的发射。以下是幕后发生的事情: (1)SQLite
支持在表中发生 DELETE
、UPDATE
或 INSERT
时触发的触发器。 (2)Room
创建了一个 InvalidationTracker
,它使用 Observers
来跟踪观察到的表中何时发生了变化。 (3)LiveData
和 Flowable
查询都依赖于 InvalidationTracker.Observer#onInvalidated
通知,收到此信息后,它会触发重新查询。 Room
只知道该表已被修改,但不知道为什么以及发生了什么变化。因此,重新查询后,查询的结果由 LiveData
或 Flowable
发出。由于 Room
没有在内存中保存任何数据,并且不能假设对象具有 equals()
,因此它无法判断这是否是相同的数据。你需要确保你的 DAO
过滤排放并且只对不同的对象做出反应。 如果 observable
查询是使用 Flowables
实现的,请使用 Flowable#distinctUntilChanged
。
@Dao
abstract class UserDao : BaseDao< UserEntity> ( ) {
@Query ( "SELECT * FROM user WHERE uid = :uid" )
protected abstract fun queryByUid ( uid: Int) : Flowable< UserEntity>
fun queryDistinctByUid ( uid: Int) : Flowable< UserEntity> = queryByUid ( id) . distinctUntilChanged ( )
}
如果您的查询返回 LiveData
,您可以使用仅允许来自源的不同对象发射的 MediatorLiveData
。
fun < T> LiveData< T> . getDistinct ( ) : LiveData< T> {
val distinctLiveData = MediatorLiveData< T> ( )
distinctLiveData. addSource ( this , object : Observer< T> {
private var initialized = false
private var lastObj: T? = null
override fun onChanged ( obj: T? ) {
if ( ! initialized) {
initialized = true
lastObj = obj
distinctLiveData. postValue ( lastObj !! )
} else if ( ( obj == null && lastObj != null ) || obj != lastObj) {
lastObj = obj
distinctLiveData. postValue ( lastObj !! )
}
}
} )
return distinctLiveData
}
在您的 DAO
中,将返回不同 LiveData
的方法设为 public
和查询受保护的数据库的方法。
@Dao
abstract class User2Dao: BaseDao< UserEntity> ( ) {
@Query ( "SELECT * FROM user WHERE uid = :uid" )
protected abstract fun queryByUid ( uid: Int) : LiveData< UserEntity>
fun queryDistinctByUid ( uid: Int) : LiveData< UserEntity> = queryByUid ( uid) . getDistinct ( )
}
5、Room 数据库增量迁移
5.1 自动迁移
注意:Room
自动迁移依赖于为旧版和新版数据库生成的数据库架构。如果 exportSchema
设为 false
,或者如果您尚未使用新版本号编译数据库,自动迁移将会失败。
@Database ( version = 1 , entities = [ User:: class ] )
abstract class AppDatabase : RoomDatabase ( ) {
.. .
}
@Database ( version = 2 , entities = [ User:: class ] ,
autoMigrations = [
AutoMigration ( from = 1 , to = 2 )
]
)
abstract class AppDatabase : RoomDatabase ( ) {
.. .
}
5.2 手动迁移
val MIGRATION_1_2 = object : Migration ( 1 , 2 ) {
override fun migrate ( database: SupportSQLiteDatabase) {
database. execSQL ( "CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " +
"PRIMARY KEY(`id`))" )
}
}
val MIGRATION_2_3 = object : Migration ( 2 , 3 ) {
override fun migrate ( database: SupportSQLiteDatabase) {
database. execSQL ( "ALTER TABLE Book ADD COLUMN pub_year INTEGER" )
}
}
Room. databaseBuilder ( applicationContext, MyDb:: class . java, "database-name" )
. addMigrations ( MIGRATION_1_2, MIGRATION_2_3) . build ( )
6、Room 数据库文件备份拷贝
建立好的数据库 db
文件都存在于 data/data/包名/databases
目录下,一般会对应有三个文件: (1)xxx.db
文件:数据库文件 (2)xxx.db-shm
文件:
db-shm
文件是共享内存文件,仅当 SQLite
以 WAL
(预写日志)模式运行时才存在。 这是因为在 WAL
模式下,共享同一个 db
文件的数据库连接必须全部更新同一存储位置(用作 WAL
文件的索引),以防止发生冲突。 (3)xxx.db-wal
文件:
wal
意思是 write-ahead log
,顾名思义就是保存的一个日志,对于提交/回滚目的很有用。sqlite 3.7
之后开始提供这个功能,当一个数据库采用 WAL
模式,所有连接数据的操作都必须使用 WAL
,然后在在数据库文件夹下生成一个后缀为 .db-wal
的文件保存操作日志。该日志使 SQLite
可以在事务失败时回滚更改。SQLite
如何使用它们以及为什么将它们保留这么长时间取决于 SQLite
的作者。如果数据库未在运行,则删除该文件是完全可以的,实际上,如果存在该文件,它将在重新启动数据库时自动删除(因为它仅在数据库正在主动写入/提交数据时才有用) 参考资料:sqlite
官网:https://www.sqlite.org/fileformat2.html 所以咱们对 Room
数据库文件的备份拷贝的时候,只需要对位于 data/data/包名/databases
目录下的三个文件进行备份拷贝就行了。 数据库打开的工具可以使用:DB Browser for SQLite
,选择 SQLCipher 4 defaults
选项打开,我使用对应的版本是 3.11.2
,仅供参考。
7、Room 数据库文件加密
调研了市场目前存在针对于 Room 数据库加密的方案后,SqlCipher
相对成熟并稳定。 第一步:导包
dependencies {
implementation "net.zetetic:android-database-sqlcipher:4.5.1"
implementation "androidx.sqlite:sqlite:2.2.0"
}
abstract class AppDatabase: RoomDatabase ( ) {
private val factory = SupportFactory ( SQLiteDatabase. getBytes ( charArrayOf ( '1' , '2' , '3' , '4' ) ) )
}
Room. databaseBuilder (
context. applicationContext, AppDatabase:: class . java, "database_ ${ loginId } .db"
) . openHelperFactory ( factory) . build ( )
数据库打开的工具可以使用:DB Browser for SQLite
,选择 SQLCipher 4 defaults
选项打开,我使用对应的版本是 3.11.2
,仅供参考。
8、Room 数据库单元测试
android {
compileSdkVersion 31
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
testImplementation "androidx.room:room-testing:$room_version"
}
@RunWith ( AndroidJUnit4:: class )
class UserDaoTest {
private lateinit var appDatabase: AppDatabase
private lateinit var dao: UserDao
@Before
fun createDb ( ) {
val context = ApplicationProvider. getApplicationContext< Context> ( )
appDatabase = Room. inMemoryDatabaseBuilder ( context, AppDatabase:: class . java) . build ( )
dao = appDatabase. userDao ( )
}
@After
@Throws ( IOException:: class )
fun closeDb ( ) {
appDatabase. close ( )
}
private fun createEntity ( uid: Int) = UserEntity (
uid = uid,
sid = System. currentTimeMillis ( ) , cid = System. currentTimeMillis ( ) ,
subIndex = ( 0 .. 100 ) . random ( ) , name = "张三" ,
age = ( 0 .. 100L ) . random ( ) , bool = true
)
@Test
fun insert ( ) {
val entity = createEntity ( 1 )
val insert = dao. insert ( entity)
Assert. assertEquals ( listOf ( entity) , dao. queryAll ( ) )
}
@Test
fun delete ( ) {
val entityArray = arrayOf (
createEntity ( 1 ) ,
createEntity ( 2 )
)
dao. insert ( * entityArray)
val delete = dao. delete ( entityArray[ 0 ] )
Assert. assertEquals ( 1 , delete)
Assert. assertEquals ( listOf ( entityArray[ 1 ] ) , dao. queryAll ( ) )
}
@Test
fun deleteAll ( ) {
val entityArray = arrayOf (
createEntity ( 1 ) ,
createEntity ( 2 )
)
dao. insert ( * entityArray)
val delete = dao. deleteAll ( )
Assert. assertEquals ( 2 , delete)
}
@Test
fun update ( ) {
val entityArray = arrayOf (
createEntity ( 1 ) ,
createEntity ( 2 )
)
dao. insert ( * entityArray)
entityArray[ 0 ] . name = "testUpdate1"
entityArray[ 1 ] . name = "testUpdate2"
val update = dao. update ( * entityArray)
Assert. assertEquals ( 2 , update)
Assert. assertEquals ( entityArray. toList ( ) , dao. queryAll ( ) )
}
@Test
fun queryAll ( ) {
val entityArray = arrayOf (
createEntity ( 1 ) ,
createEntity ( 2 )
)
dao. insert ( * entityArray)
Assert. assertEquals ( entityArray. toList ( ) , dao. queryAll ( ) )
}
}
第三步:执行所有的测试用例,如果没有问题就说明接口的调用都是可以跑通的。
9、参考文献
Room 官方开发文档 https://www.sqlite.org/fileformat2.html https://medium.com/androiddevelopers/7-pro-tips-for-room-fbadea4bfbd1 https://blog.csdn.net/qq_31469589/article/details/114950653 等等…