7个使用房间的有用技巧

7个使用房间的有用技巧


Room是SQLite之上的抽象层,可简化存储。 如果您不熟悉Room,请查看以下介绍性文章:


使用Room的7个步骤。 将应用程序迁移到Room的演练

在本文中,我想分享一些有关如何尽可能高效地使用Room的技巧。


1.预填充数据库


您是否需要在创建数据库后或首次访问数据库时立即将默认数据添加到数据库中? 使用RoomDatabase#回调 。 创建数据库时调用addCallback方法,并覆盖onCreateonOpen


在创建表后立即在第一次创建数据库时调用onCreate 。 打开数据库时onOpen 。 由于只有在完成这些方法之后才可以访问DAO,因此我们创建了一个新流,在其中获得到数据库的链接,然后获得DAO并插入必要的数据。


 Room.databaseBuilder(context.applicationContext, DataDatabase::class.java, "Sample.db") // prepopulate the database after onCreate was called .addCallback(object : Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) // moving to a new thread ioThread { getInstance(context).dataDao() .insert(PREPOPULATE_DATA) } } }) .build() 

这里查看完整的示例。


注意:如果使用ioThread方法,并且在第一次启动时应用程序崩溃,那么在创建数据库和插入数据库之间,数据将永远不会被插入。


2.使用DAO继承功能


您的数据库中是否有多个表,并复制相同的insertupdatedelete方法? DAO支持继承,因此请创建BaseDao<T>类,并在其中定义常用方法@Update @Delete@Update@Delete 。 让每个DAO扩展BaseDao并添加特定于它们的方法。


 interface BaseDao<T> { @Insert fun insert(vararg obj: T) } @Dao abstract class DataDao : BaseDao<Data>() { @Query("SELECT * FROM Data") abstract fun getData(): List<Data> } 

在这里查看详细信息。


DAO必须是接口或抽象类,因为Room会在编译时生成其实现,包括BaseDao方法。


3.在没有样板代码的事务中执行查询


使用@Transaction注释方法可确保您在此方法中执行的所有数据库操作都在同一事务中执行。 如果方法主体中发生异常,则不会执行事务。


 @Dao abstract class UserDao { @Transaction open fun updateData(users: List<User>) { deleteAllUsers() insertAll(users) } @Insert abstract fun insertAll(users: List<User>) @Query("DELETE FROM Users") abstract fun deleteAllUsers() } 

在以下情况下,您可能希望对使用select语句的@Transaction方法使用@Transaction批注:


  • 当查询结果相当大时。 通过在一个事务中发出请求,您可以保证,如果请求的结果不适合游标的一个“部分”,则不会因游标排列之间的数据库更改而损坏请求。
  • 当查询结果是带有@Relation字段的POJO时。 每个字段本身就是一个请求,因此在一个事务中运行它们可以确保请求之间的结果一致。

具有多个参数的@Delete@Update@Insert在事务内自动启动。


4.只阅读您需要的内容


向数据库发出请求时,是否使用响应中获得的所有字段? 请注意您的应用程序使用的内存量,并仅下载最终将要使用的那些字段。 通过减少I / O的成本,这还将提高您的请求速度。 Room将为您完成列与对象之间的映射。


考虑以下复杂的User对象:


 @Entity(tableName = "users") data class User(@PrimaryKey val id: String, val userName: String, val firstName: String, val lastName: String, val email: String, val dateOfBirth: Date, val registrationDate: Date) 

在某些屏幕上,我们不需要显示所有这些信息。 因此,相反,我们可以创建一个仅包含必需数据的UserMinimal对象。


 data class UserMinimal(val userId: String, val firstName: String, val lastName: String) 

在DAO类中,我们定义查询并从users表中选择正确的列。


 @Dao interface UserDao { @Query(“SELECT userId, firstName, lastName FROM Users) fun getUsersMinimal(): List<UserMinimal> } 

5.使用外键控制实体之间的依赖关系


即使Room 不直接支持实体之间的关系,它仍允许您使用外键定义对象之间的依赖关系。


Room具有@ForeignKey批注,它是@Entity批注的一部分。 在功能上,它类似于SQLite中的外键 。 当对数据库进行更改时,它保证保留实体之间的关系。 要添加它,请定义要引用的对象,以及当前对象中的列和要引用的卷。


考虑类UserPetPet具有所有者-外键引用的用户ID。


 @Entity(tableName = "pets", foreignKeys = arrayOf( ForeignKey(entity = User::class, parentColumns = arrayOf("userId"), childColumns = arrayOf("owner")))) data class Pet(@PrimaryKey val petId: String, val name: String, val owner: String) 

如果愿意,您可以确定在数据库中删除或更新父项时应采取的措施。 您可以选择以下选项之一: NO_ACTIONRESTRICTSET_NULLSET_DEFAULTCASCADE ,其行为与SQLite中的相同。


注意:在Room中, SET_DEFAULT作用类似于SET_NULL ,因为 Room尚未允许您设置列的默认值。


6.使用@Relation简化一对多查询


在前面的“ User - Pet示例中,我们可以说存在一对多关系:用户可以拥有多个宠物。 假设我们要获取一个带有宠物的用户列表: List<UserAndAllPets>


 data class UserAndAllPets (val user: User, val pets: List<Pet> = ArrayList()) 

要手动执行此操作,我们需要进行两个查询:一个查询获取所有用户的列表,另一个查询根据用户ID获取宠物的列表。


 @Query(“SELECT * FROM Users”) public List<User> getUsers(); @Query(“SELECT * FROM Pets where owner = :userId”) public List<Pet> getPetsForUser(String userId); 

然后,我们将遍历用户列表并每次访问Pets表。


@Relation注释将使我们的生活更轻松:它将自动请求相关对象。 @Relation只能应用于ListSet 。 更新UserAndAllPets类:


 class UserAndAllPets { @Embedded var user: User? = null @Relation(parentColumn = “userId”, entityColumn = “owner”) var pets: List<Pet> = ArrayList() } 

在DAO中,我们定义了一个查询,Room将查询UsersPets表并自行映射对象。


 @Transaction @Query(“SELECT * FROM Users”) List<UserAndAllPets> getUsers(); 

7.避免对可观察到的请求发出错误通知


假设您想使用可观察的请求按用户的标识符获取用户:


 @Query(“SELECT * FROM Users WHERE userId = :id) fun getUserById(id: String): LiveData<User> // or @Query(“SELECT * FROM Users WHERE userId = :id) fun getUserById(id: String): Flowable<User> 

每次更新时,您都会收到一个新的User对象。 但是,如果在Users表中发生其他更改(删除,更新或插入),而这些更改与您感兴趣的用户无关,则也会收到此对象,这将导致错误的通知。 此外,如果您的查询包含多个表,则只要其中任何一个表发生更改,您都会收到新消息。


这是幕后发生的事情:


  1. 当表中发生DELETEUPDATEINSERT时,SQLite会触发触发器
  2. Room创建一个InvalidationTracker ,该InvalidationTracker使用Observers来跟踪观察表中的所有更改。
  3. LiveDataLiveData请求都依赖于InvalidationTracker.Observer#onInvalidated通知 。 收到请求后,便会重复请求。

Room仅知道表已更改,但不知道为什么以及更改了什么。 因此,在第二个请求之后,使用LiveDataLiveData传输请求的结果。 因为 Room不会在内存中存储任何数据;它无法确定是否为相同数据。


您必须确保您的DAO 过滤请求并仅响应必要的对象。


如果可观察查询是使用Flowables实现的,请使用Flowable #diverUntilChanged


 @Dao abstract class UserDao : BaseDao<User>() { /** * Get a user by id. * @return the user from the table with a specific id. */ @Query(“SELECT * FROM Users WHERE userid = :id”) protected abstract fun getUserById(id: String): Flowable<User> fun getDistinctUserById(id: String): Flowable<User> = getUserById(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 ,而查询数据库的方法是protected


 @Dao abstract class UserDao : BaseDao<User>() { @Query(“SELECT * FROM Users WHERE userid = :id”) protected abstract fun getUserById(id: String): LiveData<User> fun getDistinctUserById(id: String): LiveData<User> = getUserById(id).getDistinct() } 

请在此处查看完整的代码示例。


注意:如果您请求显示列表,请注意Paging Library ,它将返回LivePagedListBuilder 。 该库将帮助您自动计算列表项之间的差异并更新用户界面。


另请参阅: 使用Room的7个步骤。 将应用程序迁移到Room的演练

Source: https://habr.com/ru/post/zh-CN442786/


All Articles