原文:medium.com/androiddeve…
做者:Florina Muntenescuhtml
Room 在 SQLite 上提供了一個抽象層,方便開發者更加容易的存儲數據。若是您以前未曾接觸過 Room
,請先閱讀下面的入門文章: 7-steps-to-roomjava
在本文中,我將向你們分享一些關於使用 Room 的專業提示:android
RoomDatabase#Callback
爲 Room 設置默認數據Dao
的繼承功能@Relation
簡化一對多的查詢當新建或者打開數據庫以後,您是否須要爲其設置默認數據?使用 RoomDataBase#Callback
便可。構建 RoomDataBase
時調用 addCallback
方法,並重寫 onCreate
或者 onOpen
。git
在建立表以後,首次建立數據庫將調用 onCreate
。打開數據庫時調用 onOpen
。因爲只有在這些方法返回後,才能訪問 Dao
,經過建立一個新的線程,獲取數據庫的引用,繼而獲得 Dao
,並插入數據。github
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
時,若是您的應用程序在第一次啓動時崩潰,在數據庫建立和插入之間,將永遠不會插入數據。app
您的數據庫中是否有多張表,而且發現本身正在複製相同的 insert
,update
,delete
方法。Dao
支持繼承功能,建立一個 BaseDao<T>
類,並聲明通用的 @Insert
,@Update
,@Delete
方法。讓每一個 Dao
繼承自 BaseDao
並添加每一個 Dao
特定的方法。ide
interface BaseDao<T> {
@Insert
fun insert(vararg obj: T)
}
@Dao
abstract class DataDao : BaseDao<Data>() {
@Query("SELECT * FROM Data")
abstract fun getData(): List<Data>
}
複製代碼
點擊查看完整 示例post
Dao
必須是接口或者抽象類,由於 Room
在編譯期間生成他們的實現類,包括 BaseDao
中的方法。ui
使用 @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()
}
複製代碼
在如下狀況,您可能但願對具備查詢語句的 @Query
方法使用 @Transaction
註解。
cursor window
,則由數據庫 cursor window wraps
致使的數據庫更改,不會被破壞。@Relation
字段的 POJO
時。因爲這些字段是單獨的查詢,所以在單個事務中執行,將保證查詢結果的一致性。具備多個參數的 @Delete
,@Update
,@Insert
方法將自動在事務中執行。
當您查詢數據庫時,您是否使用查詢結果中返回的全部字段?處理應用程序使用的內存,並僅加載最終使用的字段子集。這還能夠經過下降 IO 成原本提升查詢速度。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>
}
複製代碼
儘管 Room
不直接支持 關係,但它容許您在實體類之間定義外鍵約束。
Room
擁有 @ForeignKey
註解,它是 @Entity
註解的一部分,容許使用 SQLite
的外鍵功能。它會跨表強制執行約束,以確保在修改數據庫時關係有效。在實體類中,定義 要引用的父實體,父實體的列 以及 當前實體中的列。
思考 User
和 Pet
類。Pet
有一個 owner
字段,它是一個引用爲外鍵的 user 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_ACTION
, RESTRICT
,SET_NULL
,SET_DEFAULT
, 或者 CASCADE
,這與 SQLite
具備相同的行爲。
注意: 在 Room
中,SET_DEFAULT
用做 SET_NULL
。由於 Room
尚不容許爲列設置默認值。
在以前的 User
- Pet
示例中,設定存在 一對多 的關係:一個用戶能夠擁有多隻寵物。假設咱們想得到擁有寵物的用戶列表:List<UserAndAllPets>
。
data class UserAndAllPets (val user: User,
val pets: List<Pet> = ArrayList())
複製代碼
要手動執行此操做,咱們須要實現 2 個查詢:獲取全部用戶的列表 和 根據用戶 ID 獲取寵物列表
@Query(「SELECT * FROM Users」)
public List<User> getUsers();
@Query(「SELECT * FROM Pets where owner = :userId」)
public List<Pet> getPetsForUser(String userId);
複製代碼
而後咱們將遍歷用戶列表並查詢 Pets
表。
爲了簡化上述操做,Room
提供 @Relation
註解能夠自動獲取相關實體。@Relation
只能用於 List
或者 Set
對象。修改後的實體類以下所示:
class UserAndAllPets {
@Embedded
var user: User? = null
@Relation(parentColumn = 「userId」, entityColumn = 「owner」)
var pets: List<Pet> = ArrayList()
}
複製代碼
在 Dao
中,咱們只需聲明一個查詢。 Room
將查詢 Users
和 Pets
表並處理對象映射。
@Transaction
@Query(「SELECT * FROM Users」)
List<UserAndAllPets> getUsers();
複製代碼
假設您但願經過用戶 id
獲取用戶,並將查詢結果做爲一個可觀察的對象返回:
@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
表發生與您感興趣的用戶,無關的其餘操做(刪除,更新或插入)時,您也將得到相同的對象,從而致使錯誤通知。更重要的是,若是涉及到多表查詢,那麼只要其中的一個表發生變化,您將會得到新的對象。
這是幕後發生的事情:
DELETE
,UPDATE
,INSERT
時,SQLite
將觸發 觸發器。Room
建立一個 InvalidationTracker
,它使用 Observers
跟蹤觀察到的表中發生了什麼變化。LiveData
和 Flowable
查詢都依賴於 InvalidationTracker.Observer#onInvalidated
通知。收到此通知後,將觸發從新查詢。Room
只知道表已經被修改,但不知道爲何和修改了什麼。所以,在從新查詢後,查詢到的結果將由 LiveData
和 Flowable
發射。因爲 Room
在內存中不保存任何數據,而且不能假設對象具備 equals()
,所以沒法判斷這是不是相同的數據。
你須要確保 Dao
可以過濾發射的數據,而且只對不一樣的對象作出響應。
若是使用 Flowable
實現可觀察的查詢,請使用 Flowable#distinctUntilChanged
@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
}
複製代碼
在 Daos
中,定義一個 public
字段修飾,返回不一樣的 LiveData
對象的方法, 以及 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
。由於該庫將自動計算 Item
之間的差別,並更新 UI
。
若是你是 Room
新手,請查閱咱們以前的文章: