android ROOM kotlin官方文檔完全學習2.6
使用 Room 將數據保存到本地數據庫 | Android Developers (google.cn)
一、簡介
1.1 引入
dependencies {def room_version = "2.6.1"implementation "androidx.room:room-runtime:$room_version"//如下三選一annotationProcessor "androidx.room:room-compiler:$room_version"kapt "androidx.room:room-compiler:$room_version"ksp "androidx.room:room-compiler:$room_version"// optional - RxJava2 support for Roomimplementation "androidx.room:room-rxjava2:$room_version"// optional - RxJava3 support for Roomimplementation "androidx.room:room-rxjava3:$room_version"// optional - Guava support for Room, including Optional and ListenableFutureimplementation "androidx.room:room-guava:$room_version"// optional - Test helperstestImplementation "androidx.room:room-testing:$room_version"// optional - Paging 3 Integrationimplementation "androidx.room:room-paging:$room_version"
}
1.2 三大組件
- DataBase,用于保存數據庫并作為應用持久性數據底層連接的主要訪問點。
- Entity,用于表示應用的數據庫中的表。
- DAO(數據訪問對象),為您的應用提供在數據庫中查詢、更新、插入和刪除數據的方法。
1.3 快速示例
1.3.1 定義User類
定義User數據實體。每個實例代表數據庫user表的一行。
@Entity
data class User(@PrimaryKey val uid: Int,@ColumnInfo(name = "first_name") val firstName: String?,@ColumnInfo(name = "last_name") val lastName: String?
)
詳細使用參考第二章【使用Room實體定義數據】。
1.3.2 數據訪問對象(DAO)
用來操控user表的數據交互的方法。
@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>@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +"last_name LIKE :last LIMIT 1")fun findByName(first: String, last: String): User@Insertfun insertAll(vararg users: User)@Deletefun delete(user: User)
}
詳細使用參考第三章【使用DAO訪問數據】。
1.3.3 數據庫
作為應用對持久性數據的主要訪問點。幾個條件如下:
-
使用@Database注解,列出所有的entities。
-
必須抽象類,繼承自RoomDatabase。
-
每一個DAO類,都需要一個定義一個零參數的抽象函數,返回DAO類的實例。
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {abstract fun userDao(): UserDaocompanion object {private var instance:AppDatabase? = nullprivate const val DATA_BASE_NAME = "app"private val lock = Any()/*** 調用單例數據庫*/val db : AppDatabaseget() {if (instance == null) {synchronized(lock) {if (instance == null) {instance = Room.databaseBuilder(Globals.app,AppDatabase::class.java,DATA_BASE_NAME)//.enableMultiInstanceInvalidation() 多進程啟用 todo.build()}}}return instance!!}}
}//使用的方法:
val userDao = AppDatabase.db().userDao()
val users: List<User> = userDao.getAll()
二、使用 Room 實體定義數據
2.1 實體詳解(@Entity, tableName)
@Entity(tableName = "users")
data class User(@PrimaryKey val id: Int,val firstName: String?,val lastName: String?
)
必須是public或者有get,set函數。定義為每一列。
類名就是就是數據表名,可以通過@entity里面的tableName修改。
字段名可以通過ColumnInfo的name修改。
表和列名都不區分大小寫。
2.2 主鍵(@PrimaryKey, autoGenerate)
必須定義主鍵,并使用@PrimaryKey注解。
@PrimaryKey val id: Int
由于默認是@PrimaryKey(autoGenerate = false)是false,所以必須自行管理id的唯一性,所以一般情況,我們需要設置為true。
2.3 復合主鍵(@Entity primaryKeys)
@Entity(primaryKeys = ["firstName", "lastName"])
data class User(val firstName: String?,val lastName: String?
)
如果您需要通過多個列的組合對實體實例進行唯一標識,則可以通過列出@Entity primaryKeys屬性中的以下列定義一個復合主鍵。
2.4 忽略字段(@Ignore,@Entity ignoredColumns)
@Entity
data class User(@PrimaryKey val id: Int,val firstName: String?,val lastName: String?,@Ignore val picture: Bitmap?
)
如果是繼承,想忽略父類里面的字段,通過@Entity(ignoredColumns = [ ])處理:
open class User {var picture: Bitmap? = null
}@Entity(ignoredColumns = ["picture"])
data class RemoteUser(@PrimaryKey val id: Int,val hasVpn: Boolean
) : User()
三、使用DAO訪問數據
可以定義為接口或者抽象類。使用@Dao進行注解。示例:
@Dao
interface UserDao {@Insertfun insertAll(vararg users: User)@Deletefun delete(user: User)@Query("SELECT * FROM user")fun getAll(): List<User>
}
有2種方式定義函數:
- 3.1 不用編寫SQL代碼,實現
插入
,更新
,刪除
數據庫的行。 - 3.2 自行編寫SQL
查詢
。
3.1 便捷方法
Insert
@Dao
interface UserDao {@Insert(onConflict = OnConflictStrategy.REPLACE) //遇到有數據就替換,可以選擇ABORT,IGNOREfun insertUsers(vararg users: User)@Insertfun insertBothUsers(user1: User, user2: User)@Insertfun insertUsersAndFriends(user: User, friends: List<User>)
}
自己可以追加返回值,單入參就返回long型的rowId。多入參就定義返回值,數組型的rowId。TODO驗證是需要定義還是自行追加。
Update
@Dao
interface UserDao {@Updatefun updateUsers(vararg users: User)
}
可以追加返回值,指示成功更新的行數。TODO驗證是否會優先插入。
現在Insert和Update都只有三個OnConflictStrategy可選,REPLACE,IGNORE和ABORT。
Delete
@Dao
interface UserDao {@Deletefun deleteUsers(vararg users: User)
}
可以刪除1個或者多個。會根據主鍵去刪除,如果沒有相同主鍵的行,就不會有任何改變。可以追加返回值,返回的是int的刪除行數。
3.2 復雜方法(Query)
不要認為query就是查詢了。@Query注解,可以自定義SQL語句。有個優點是編譯就會報錯來提示你。
簡單查詢
@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>
查詢所有User對象。
返回表中多列的子集
- 通過定義一個簡單的類:
data class NameTuple(@ColumnInfo(name = "first_name") val firstName: String?,@ColumnInfo(name = "last_name") val lastName: String?
)
- 定義DAO函數:
@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>
簡單參數傳遞
傳參的目的為了過濾操作。例如,以下代碼定義了一個返回超過特定年齡的所有用戶的方法:
@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>@Query("SELECT * FROM user WHERE first_name LIKE :search " +"OR last_name LIKE :search")
fun findUserWithName(search: String): List<User>
將一組參數傳遞
@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>
3.5 返回Cursor
@Dao
interface UserDao {@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")fun loadRawUsersOlderThan(minAge: Int): Cursor
}
不推薦,因為它無法保證行是否存在或行包含哪些值。
四、定義對象之間的關系
4.1 兩張表之間的混合查詢
在 Room 中,您可以通過兩種方式定義和查詢實體之間的關系:
-
使用具有嵌入式對象的中間數據類。
-
使用具有多重映射返回值類型的關系型查詢方法。
中間數據類
在中間數據類方法中,您可以定義數據類,以便在 Room 實體之間建立關系。此數據類保存一個實體的實例與另一個實體的實例之間的配對(作為嵌入式對象)。然后,查詢方法可以返回此數據類的實例,以供您的應用使用。
例如,您可以定義 UserBook
數據類來表示已借閱特定圖書的圖書館用戶,并定義一個查詢方法用于從數據庫中檢索 UserBook
實例的列表:
@Dao
interface UserBookDao {@Query("SELECT user.name AS userName, book.name AS bookName " +"FROM user, book " +"WHERE user.id = book.user_id")fun loadUserAndBookNames(): LiveData<List<UserBook>>
}data class UserBook(val userName: String?, val bookName: String?)
返回Map(推薦)
room2.4支持如下操作:
@Query("SELECT * FROM user" +"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>
4.2 創建嵌套對象(@Embeded)
有時,您可能希望在數據庫邏輯中將某個實體或數據對象表示為一個緊密的整體,即使該對象包含多個字段也是如此。在這些情況下,您可以使用 @Embedded
注釋表示要分解為表格中的子字段的對象。然后,您可以像查詢其他各個列一樣查詢嵌套字段。
例如,您的 User
類可以包含一個 Address
類型的字段,它表示名為 street
、city
、state
和 postCode
的字段的組合。若要在表中單獨存儲組合列,請在帶有 @Embedded
注解的 User
類中添加 Address
字段,如以下代碼段所示:
data class Address(val street: String?,val state: String?,val city: String?,@ColumnInfo(name = "post_code") val postCode: Int
)@Entity
data class User(@PrimaryKey val id: Int,val firstName: String?,@Embedded val address: Address?
)
然后,表示 User
對象的表將包含具有以下名稱的列:id
、firstName
、street
、state
、city
和 post_code
。
七、更高級的用法
暫時不去學習,備查。查看官網。
2.5 表搜索支持
2.6 autoValue,java不可變值類
3.3 查詢多張表
您的部分查詢可能需要訪問多個表格才能計算出結果。您可以在 SQL 查詢中使用 JOIN
子句來引用多個表。
以下代碼定義了一種方法將三個表進行聯接,以便返回當前已出借給特定用戶的圖書:
@Query("SELECT * FROM book " +"INNER JOIN loan ON loan.book_id = book.id " +"INNER JOIN user ON user.id = loan.user_id " +"WHERE user.name LIKE :userName"
)
fun findBooksBorrowedByNameSync(userName: String): List<Book>
進階
此外,您還可以定義簡單對象以從多個聯接表返回若干列的子集,如【返回表中多列的子集】部分所述。以下代碼定義了一個 DAO,其中包含一個返回用戶姓名和所借閱圖書名稱的方法:
interface UserBookDao {@Query("SELECT user.name AS userName, book.name AS bookName " +"FROM user, book " +"WHERE user.id = book.user_id")fun loadUserAndBookNames(): LiveData<List<UserBook>>// You can also define this class in a separate file.data class UserBook(val userName: String?, val bookName: String?)
}
進階2-返回Map
//您可以直接從您的查詢方法返回 `User` 和 `Book` 的映射,而不是返回保存有 `User` 和 `Book` 實例配對的自定義數據類的實例列表。
@Query("SELECT * FROM user" +"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>//GROUP BY以便利用 SQL 的功能進行高級計算和過濾。
@Query("SELECT * FROM user" +"JOIN book ON user.id = book.user_id" +"GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
fun loadUserAndBookNames(): Map<User, List<Book>>//如果您不需要映射整個對象,還可以通過在查詢方法的 @MapInfo 注解中設置 keyColumn 和 valueColumn 屬性,返回查詢中特定列之間的映射:
@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query("SELECT user.name AS username, book.name AS bookname FROM user" +"JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<String, List<String>>