1、導(dǎo)入room庫
項目app.gradle導(dǎo)入
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
// 如果編譯用kapt 導(dǎo)入這個
kapt "androidx.room:room-compiler:$room_version"
// 如果編譯用ksp導(dǎo)入這個
ksp "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
2、Room 包含 3 個主要組件:
-
數(shù)據(jù)庫:包含數(shù)據(jù)庫持有者,并作為應(yīng)用已保留的持久關(guān)系型數(shù)據(jù)的底層連接的主要接入點(diǎn)。
使用 [@Database`注釋的類應(yīng)滿足以下條件:
- 是擴(kuò)展
RoomDatabase的抽象類。 - 在注釋中添加與數(shù)據(jù)庫關(guān)聯(lián)的實體列表。
- 包含具有 0 個參數(shù)且返回使用
@Dao注釋的類的抽象方法。
在運(yùn)行時,您可以通過調(diào)用
Room.databaseBuilder()獲取Database的實例。 - 是擴(kuò)展
Entity:表示數(shù)據(jù)庫中的表。
DAO:包含用于訪問數(shù)據(jù)庫的方法。
例如:
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
@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
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).build()
注意:如果您的應(yīng)用在單個進(jìn)程中運(yùn)行,在實例化 AppDatabase 對象時應(yīng)遵循單例設(shè)計模式。每個 RoomDatabase 實例的成本相當(dāng)高,而您幾乎不需要在單個進(jìn)程中訪問多個實例。
如果您的應(yīng)用在多個進(jìn)程中運(yùn)行,請在數(shù)據(jù)庫構(gòu)建器調(diào)用中包含 enableMultiInstanceInvalidation()。這樣,如果您在每個進(jìn)程中都有一個 AppDatabase 實例,可以在一個進(jìn)程中使共享數(shù)據(jù)庫文件失效,并且這種失效會自動傳播到其他進(jìn)程中 AppDatabase 的實例。
定義實體類
@Entity(tableName = "user")
class User {
@PrimaryKey(autoGenerate = true)
var uid: Int? = null
@ColumnInfo(name = "first_name")
var firstName: String = ""
@ColumnInfo(name = "last_name")
var lastName: String = ""
@Ignore
var memo: String = ""
}
必須要有主鍵,可以使用@PrimaryKey (autoGenerate = true) ,autoGenerate表示自動生成。 @Ignore表示不保存這個字段
使用DAO訪問數(shù)據(jù)
Insert
@Dao
interface UserDao {
@Insert
fun insertUser(user: User):Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
}
如果 @Insert方法只接收 1 個參數(shù),則它可以返回 long,這是插入項的新 rowId。如果參數(shù)是數(shù)組或集合,則應(yīng)返回 long[] 或 List<Long>。
onConflict = OnConflictStrategy.REPLACE,表示如已有數(shù)據(jù),就覆蓋掉。數(shù)據(jù)的判斷通過主鍵進(jìn)行匹配,也就是id,非整個EquipType對象。
Update
@Dao
interface UserDao {
@Update
fun updateUser(user: User)
@Update
fun updateUsers(vararg user: User)
}
update根據(jù)數(shù)據(jù)的主鍵去判斷和更新數(shù)據(jù)
Delete
@Dao
interface UserDao {
@Delete
fun deleteUser(user:User)
@Delete
fun deleteUsers(vararg user:User)
}
delete根據(jù)數(shù)據(jù)的主鍵去判斷和刪除數(shù)據(jù)
Query
@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
}
可以在sql語句中使用冒號:去引用方法傳遞過來的參數(shù)。可以傳遞多個參數(shù),也可以傳遞集合。
如果一個表字段很多,可以創(chuàng)建一個新的數(shù)據(jù)類,注明需要的參數(shù),例如:
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 user")
fun loadFullName(): List<NameTuple>
}
使用流進(jìn)行響應(yīng)式查詢
場景 如果有一個用戶列表界面,添加了一個用戶,需要刷新列表,可以使用協(xié)程的Flow,Room的查詢可以返回Flow,一旦數(shù)據(jù)有變化,會重新觸發(fā)一次查詢,返回新的數(shù)據(jù)。
interface UserDao {
@Query("SELECT * FROM user")
fun getAll():Flow <List<User>>
}
lifecycleScope.launch() {
val list = MyApplication.instance().db.userDao()
.getAll()
list.collect(object : FlowCollector<List<User>> {
override suspend fun emit(value: List<User>) {
for (user in value) {
Log.d("haha", user.uid.toString() + ":" + user.firstName)
}
}
})
}
getAll先會查詢一次數(shù)據(jù),然后每次表數(shù)據(jù)變更,都會重新觸發(fā)一次getAll的數(shù)據(jù)查詢。
使用ViewModel+LiveData
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll():LiveData <List<User>>
}
class OneFragmenModel : ViewModel() {
val list: LiveData<List<User>> = MyApplication.instance().db.userDao().getAll()
}
oneFragmenModel.list.observe(viewLifecycleOwner,Observer<List<User>>{
for (user in it){
Log.d("hahaha",user.uid.toString()+" "+user.firstName)
}
})
使用事務(wù)
使用注解@Transaction,可以實現(xiàn)事務(wù)
@Dao
interface UserDao {
@Delete
fun delete(user: User)
@Transaction
fun deleteAndInsertUser(user:User){
delete(user)
insertUser(user)
}
@Insert
fun insertUser(user: User):Long
}
定義對象之間的關(guān)系
Room不允許對象互相引用,但是可以定義幾種關(guān)系
1、創(chuàng)建嵌套對象
在數(shù)據(jù)庫邏輯中將某個實體或數(shù)據(jù)對象表示為一個緊密的整體,即使該對象包含多個字段也是如此。在這些情況下,您可以使用 @Embedded 注釋表示要分解為表格中的子字段的對象。然后,可以像查詢其他各個列一樣查詢嵌套字段。
@Database(entities = arrayOf(User::class,Address::class), version = 3)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
@Entity
class User {
@PrimaryKey(autoGenerate = true)
var uid: Int? = null
@ColumnInfo(name = "first_name")
var firstName: String = ""
@ColumnInfo(name = "last_name")
var lastName: String = ""
@Embedded
var address: Address? = null
}
@Entity
class Address {
@PrimaryKey(autoGenerate = true)
var addressId: Int? = null
@ColumnInfo(name = "address_name")
var addressName: String = ""
}

數(shù)據(jù)存儲結(jié)構(gòu),實際上在user表里面增加了列,去存儲嵌套對象,會忽略嵌套對象的主鍵。
2、定義一對一關(guān)系
比如一個人有一個身份證
@Entity
class User {
@PrimaryKey(autoGenerate = true)
var uid: Long? = null
@ColumnInfo(name = "first_name")
var firstName: String = ""
@ColumnInfo(name = "last_name")
var lastName: String = ""
@Embedded
var address: Address? = null
}
@Entity
class IDCard {
@PrimaryKey(autoGenerate = true)
var id: Int? = null
@ColumnInfo
var idNum: String? = null
@ColumnInfo
var userId: Long? = null
}
如果需要查詢?nèi)撕蜕矸葑C一起查詢出來,需要建立一個新的類,對2個實體進(jìn)行關(guān)聯(lián)。注意@Embedded不要掉了。
data class UserAndIDCard(
@Embedded
val user: User,
@Relation(
parentColumn = "uid",
entityColumn = "userId"
)
val idCard: IDCard
)
@Dao
interface UserDao {
@Transaction
@Query("SELECT * FROM User")
fun getUsersAndIDCard(): List<UserAndIDCard>
}
如果要一起插入,好像是不行的,我用這個關(guān)聯(lián)關(guān)系的類,直接用@Insert,是會報錯的。目前只能2個表分開單獨(dú)插入 例如
val userId = MyApplication.instance().db.userDao().insertUser(user)
val idCard = IDCard();
idCard.idNum = "12312412312"
idCard.userId = userId;
MyApplication.instance().db.IDCardDao().insertIDCard(idCard)
3、定義一對多
一個用戶有多本書
@Entity
class User {
@PrimaryKey(autoGenerate = true)
var uid: Long? = null
@ColumnInfo(name = "first_name")
var firstName: String = ""
@ColumnInfo(name = "last_name")
var lastName: String = ""
@Embedded
var address: Address? = null
}
@Entity
class Book {
@PrimaryKey(autoGenerate = true)
var bookId: Long? = null
@ColumnInfo(name = "book_name")
var bookName = ""
@ColumnInfo(name = "user_id")
var userId :Long? = null
}
data class UserAndBooks(
@Embedded val user: User,
@Relation(
parentColumn = "uid",
entityColumn = "user_id"
)
val bookList: List<Book>
)
@Dao
interface UserDao {
@Transaction
@Query("SELECT *FROM User")
fun getUsersAndBooks():List<UserAndBooks>
}
val userId = MyApplication.instance().db.userDao().insertUser(user)
val book1 = Book();
book1.bookName = "book1"
book1.userId = userId
val book2 = Book();
book2.bookName = "book2"
book2.userId = userId
MyApplication.instance().db.bookDao().insertBook(book1)
MyApplication.instance().db.bookDao().insertBook(book2)
val list = MyApplication.instance().db.userDao().getUsersAndBooks();
定義多對多
一首歌可以在多個播放列表,一個播放列表可以有多首歌
多對多關(guān)系與其他關(guān)系類型均不同的一點(diǎn)在于,子實體中通常不存在對父實體的引用。因此,需要創(chuàng)建第三個類來表示兩個實體之間的關(guān)聯(lián)實體(即交叉引用表)。交叉引用表中必須包含表中表示的多對多關(guān)系中每個實體的主鍵列。
@Entity
class Song {
@PrimaryKey(autoGenerate = true)
var songId: Long? = null
@ColumnInfo(name = "song_name")
var songName = ""
}
@Entity
class PlayList {
@PrimaryKey(autoGenerate = true)
var playlistId : Long? = null
@ColumnInfo(name = "playlist_name")
var playListName =""
}
@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
val playlistId: Long,
val songId: Long
)
查詢播放列表和每個播放列表所含歌曲的列表,創(chuàng)建一個新的數(shù)據(jù)類,其中包含單個 Playlist 對象,以及該播放列表所包含的所有 Song 對象的列表
查詢歌曲和每首歌曲所在播放列表的列表,創(chuàng)建一個新的數(shù)據(jù)類,其中包含單個 Song 對象,以及包含該歌曲的所有 Playlist 對象的列表。
兩種情況下,都可以通過以下方法在實體之間建立關(guān)系:在上述每個類中的 Relation注釋中使用 associateBy 屬性來確定提供 Playlist 實體與 Song 實體之間關(guān)系的交叉引用實體。不用網(wǎng)上說的entity 那樣只能查出一個數(shù)據(jù)
data class PlaylistWithSongs(
@Embedded val playlist: PlayList,
@Relation(
parentColumn = "playlistId",
entityColumn = "songId",
associateBy = Junction(PlaylistSongCrossRef::class)
)
val songs: List<Song>
)
data class SongWithPlaylists(
@Embedded val song: Song,
@Relation(
parentColumn = "songId",
entityColumn = "playlistId",
associateBy = Junction(PlaylistSongCrossRef::class)
)
val playlists: List<PlayList>
)
測試
val playList1 = PlayList()
playList1.playListName = "播放列表1"
val playList2 = PlayList()
playList2.playListName = "播放列表2"
val playList1Id = MyApplication.instance().db.playListDao().addPlayList(playList1)
val playList2Id = MyApplication.instance().db.playListDao().addPlayList(playList2)
val song1 = Song()
song1.songName = "歌曲1"
val song2 = Song()
song2.songName = "歌曲2"
val song1Id = MyApplication.instance().db.songDao().addSong(song1)
val song2Id = MyApplication.instance().db.songDao().addSong(song2)
val playlistSongCrossRef1 = PlaylistSongCrossRef(playList1Id,song1Id)
val playlistSongCrossRef2 = PlaylistSongCrossRef(playList1Id,song2Id)
val playlistSongCrossRef3 = PlaylistSongCrossRef(playList2Id,song1Id)
val playlistSongCrossRef4 = PlaylistSongCrossRef(playList2Id,song2Id)
MyApplication.instance().db.playlistSongCrossRefDao().addPlaylistSongCrossRef(playlistSongCrossRef1)
MyApplication.instance().db.playlistSongCrossRefDao().addPlaylistSongCrossRef(playlistSongCrossRef2)
MyApplication.instance().db.playlistSongCrossRefDao().addPlaylistSongCrossRef(playlistSongCrossRef3)
MyApplication.instance().db.playlistSongCrossRefDao().addPlaylistSongCrossRef(playlistSongCrossRef4)
val playlistWithSongs =
MyApplication.instance().db.playListDao().getPlaylistsWithSongs()
@Dao
interface PlayListDao {
@Transaction
@Query("SELECT * FROM PlayList")
fun getPlaylistsWithSongs(): List<PlaylistWithSongs>
@Insert
fun addPlayList(playList: PlayList):Long
}
預(yù)填充數(shù)據(jù)庫
1、從assets目錄
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.build()
2、從File
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromFile(File("mypath"))
.build()
數(shù)據(jù)庫遷移
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).addMigrations(MIGRATION_10_11).build()
val MIGRATION_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
}
}
使用 Room 引用復(fù)雜數(shù)據(jù)
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time?.toLong()
}
}
@Database(entities = arrayOf(User::class), version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
@Entity
data class User(private val birthday: Date?)
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
fun findUsersBornBetweenDates(from: Date, to: Date): List<User>
}
數(shù)據(jù)庫里面存儲的就是long
