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

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 的實例。

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

}

  
image.png

數(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

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

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

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