Jetpack學(xué)習(xí)(五) Room存儲(chǔ)

1、導(dǎo)入room庫(kù)

項(xiàng)目app.gradle導(dǎo)入

 def room_version = "2.3.0"

  implementation "androidx.room:room-runtime:$room_version"
  // 如果編譯用kapt 導(dǎo)入這個(gè)
  kapt "androidx.room:room-compiler:$room_version"
  // 如果編譯用ksp導(dǎo)入這個(gè)
  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 個(gè)主要組件:

  • 數(shù)據(jù)庫(kù):包含數(shù)據(jù)庫(kù)持有者,并作為應(yīng)用已保留的持久關(guān)系型數(shù)據(jù)的底層連接的主要接入點(diǎn)。

    使用 [@Database`注釋的類應(yīng)滿足以下條件:

    • 是擴(kuò)展 RoomDatabase 的抽象類。
    • 在注釋中添加與數(shù)據(jù)庫(kù)關(guān)聯(lián)的實(shí)體列表。
    • 包含具有 0 個(gè)參數(shù)且返回使用 @Dao注釋的類的抽象方法。

    在運(yùn)行時(shí),您可以通過(guò)調(diào)用 Room.databaseBuilder() 獲取 Database 的實(shí)例。

  • Entity:表示數(shù)據(jù)庫(kù)中的表。

  • DAO:包含用于訪問(wèn)數(shù)據(jù)庫(kù)的方法。

例如:

  @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)用在單個(gè)進(jìn)程中運(yùn)行,在實(shí)例化 AppDatabase 對(duì)象時(shí)應(yīng)遵循單例設(shè)計(jì)模式。每個(gè) RoomDatabase 實(shí)例的成本相當(dāng)高,而您幾乎不需要在單個(gè)進(jìn)程中訪問(wèn)多個(gè)實(shí)例。

如果您的應(yīng)用在多個(gè)進(jìn)程中運(yùn)行,請(qǐng)?jiān)跀?shù)據(jù)庫(kù)構(gòu)建器調(diào)用中包含 enableMultiInstanceInvalidation()。這樣,如果您在每個(gè)進(jìn)程中都有一個(gè) AppDatabase 實(shí)例,可以在一個(gè)進(jìn)程中使共享數(shù)據(jù)庫(kù)文件失效,并且這種失效會(huì)自動(dòng)傳播到其他進(jìn)程中 AppDatabase 的實(shí)例。

定義實(shí)體類

@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表示自動(dòng)生成。 @Ignore表示不保存這個(gè)字段

使用DAO訪問(wèn)數(shù)據(jù)

Insert

@Dao
interface UserDao {

    @Insert
    fun insertUser(user: User):Long
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)

}

如果 @Insert方法只接收 1 個(gè)參數(shù),則它可以返回 long,這是插入項(xiàng)的新 rowId。如果參數(shù)是數(shù)組或集合,則應(yīng)返回 long[]List<Long>。
onConflict = OnConflictStrategy.REPLACE,表示如已有數(shù)據(jù),就覆蓋掉。數(shù)據(jù)的判斷通過(guò)主鍵進(jìn)行匹配,也就是id,非整個(gè)EquipType對(duì)象。

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語(yǔ)句中使用冒號(hào):去引用方法傳遞過(guò)來(lái)的參數(shù)??梢詡鬟f多個(gè)參數(shù),也可以傳遞集合。
如果一個(gè)表字段很多,可以創(chuàng)建一個(gè)新的數(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)式查詢

場(chǎng)景 如果有一個(gè)用戶列表界面,添加了一個(gè)用戶,需要刷新列表,可以使用協(xié)程的Flow,Room的查詢可以返回Flow,一旦數(shù)據(jù)有變化,會(huì)重新觸發(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先會(huì)查詢一次數(shù)據(jù),然后每次表數(shù)據(jù)變更,都會(huì)重新觸發(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,可以實(shí)現(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
    
}

定義對(duì)象之間的關(guān)系

Room不允許對(duì)象互相引用,但是可以定義幾種關(guān)系

1、創(chuàng)建嵌套對(duì)象

在數(shù)據(jù)庫(kù)邏輯中將某個(gè)實(shí)體或數(shù)據(jù)對(duì)象表示為一個(gè)緊密的整體,即使該對(duì)象包含多個(gè)字段也是如此。在這些情況下,您可以使用 @Embedded 注釋表示要分解為表格中的子字段的對(duì)象。然后,可以像查詢其他各個(gè)列一樣查詢嵌套字段。

@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 = ""

}

  
image.png

數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),實(shí)際上在user表里面增加了列,去存儲(chǔ)嵌套對(duì)象,會(huì)忽略嵌套對(duì)象的主鍵。

2、定義一對(duì)一關(guān)系

比如一個(gè)人有一個(gè)身份證

@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一起查詢出來(lái),需要建立一個(gè)新的類,對(duì)2個(gè)實(shí)體進(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>
}

如果要一起插入,好像是不行的,我用這個(gè)關(guān)聯(lián)關(guān)系的類,直接用@Insert,是會(huì)報(bào)錯(cuò)的。目前只能2個(gè)表分開(kāi)單獨(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、定義一對(duì)多

一個(gè)用戶有多本書

@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();

定義多對(duì)多

一首歌可以在多個(gè)播放列表,一個(gè)播放列表可以有多首歌
多對(duì)多關(guān)系與其他關(guān)系類型均不同的一點(diǎn)在于,子實(shí)體中通常不存在對(duì)父實(shí)體的引用。因此,需要?jiǎng)?chuàng)建第三個(gè)類來(lái)表示兩個(gè)實(shí)體之間的關(guān)聯(lián)實(shí)體(即交叉引用表)。交叉引用表中必須包含表中表示的多對(duì)多關(guān)系中每個(gè)實(shí)體的主鍵列。

@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
)

查詢播放列表和每個(gè)播放列表所含歌曲的列表,創(chuàng)建一個(gè)新的數(shù)據(jù)類,其中包含單個(gè) Playlist 對(duì)象,以及該播放列表所包含的所有 Song 對(duì)象的列表

查詢歌曲和每首歌曲所在播放列表的列表,創(chuàng)建一個(gè)新的數(shù)據(jù)類,其中包含單個(gè) Song 對(duì)象,以及包含該歌曲的所有 Playlist 對(duì)象的列表。

兩種情況下,都可以通過(guò)以下方法在實(shí)體之間建立關(guān)系:在上述每個(gè)類中的 Relation注釋中使用 associateBy 屬性來(lái)確定提供 Playlist 實(shí)體與 Song 實(shí)體之間關(guān)系的交叉引用實(shí)體。不用網(wǎng)上說(shuō)的entity 那樣只能查出一個(gè)數(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>
)


測(cè)試

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ù)庫(kù)

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ù)庫(kù)遷移

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ù)庫(kù)里面存儲(chǔ)的就是long

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容