Jetpack学习(五) Room存储

1、导入room库

项目app.gradle导入

 def room_version = "2.3.0"

  implementation "androidx.room:room-runtime:$room_version"
  // 如果编译用kapt 导入这个
  kapt "androidx.room:room-compiler:$room_version"
  // 如果编译用ksp导入这个
  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 个主要组件:

  • 数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。

    使用 [@Database`注释的类应满足以下条件:

    • 是扩展 RoomDatabase 的抽象类。
    • 在注释中添加与数据库关联的实体列表。
    • 包含具有 0 个参数且返回使用 @Dao注释的类的抽象方法。

    在运行时,您可以通过调用 Room.databaseBuilder() 获取 Database 的实例。

  • Entity:表示数据库中的表。

  • DAO:包含用于访问数据库的方法。

例如:

  @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

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        fun loadAllByIds(userIds: IntArray): List

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

注意:如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循单例设计模式。每个 RoomDatabase 实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。

如果您的应用在多个进程中运行,请在数据库构建器调用中包含 enableMultiInstanceInvalidation()。这样,如果您在每个进程中都有一个 AppDatabase 实例,可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中 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访问数据

Insert

@Dao
interface UserDao {

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

}

如果 @Insert方法只接收 1 个参数,则它可以返回 long,这是插入项的新 rowId。如果参数是数组或集合,则应返回 long[]List
onConflict = OnConflictStrategy.REPLACE,表示如已有数据,就覆盖掉。数据的判断通过主键进行匹配,也就是id,非整个EquipType对象。

Update

@Dao
interface UserDao {
    @Update
    fun updateUser(user: User)

    @Update
    fun updateUsers(vararg user: User)
}

update根据数据的主键去判断和更新数据

Delete

@Dao
interface UserDao {
    @Delete
    fun deleteUser(user:User)
    @Delete
    fun deleteUsers(vararg user:User)
}

delete根据数据的主键去判断和删除数据

Query

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
            "last_name LIKE :last LIMIT 1")
    fun findByName(first: String, last: String): User
}

可以在sql语句中使用冒号:去引用方法传递过来的参数。可以传递多个参数,也可以传递集合。
如果一个表字段很多,可以创建一个新的数据类,注明需要的参数,例如:

   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
}

使用流进行响应式查询

场景 如果有一个用户列表界面,添加了一个用户,需要刷新列表,可以使用协程的Flow,Room的查询可以返回Flow,一旦数据有变化,会重新触发一次查询,返回新的数据。

interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll():Flow >
}

  lifecycleScope.launch() {
            val list = MyApplication.instance().db.userDao()
                .getAll()
            list.collect(object : FlowCollector> {
                override suspend fun emit(value: List) {

                    for (user in value) {

                        Log.d("haha", user.uid.toString() + ":" + user.firstName)

                    }
                }
            })
        }

getAll先会查询一次数据,然后每次表数据变更,都会重新触发一次getAll的数据查询。

使用ViewModel+LiveData

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll():LiveData >
}


class OneFragmenModel : ViewModel() {
    
    val list: LiveData> =  MyApplication.instance().db.userDao().getAll()
    
}

  oneFragmenModel.list.observe(viewLifecycleOwner,Observer>{

            for (user in it){

                Log.d("hahaha",user.uid.toString()+" "+user.firstName)

            }

        })

使用事务

使用注解@Transaction,可以实现事务

@Dao
interface UserDao {
    
    @Delete
    fun delete(user: User)

    @Transaction
    fun deleteAndInsertUser(user:User){
        delete(user)
        insertUser(user)
    }
    
    @Insert
    fun insertUser(user: User):Long
    
}

定义对象之间的关系

Room不允许对象互相引用,但是可以定义几种关系

1、创建嵌套对象

在数据库逻辑中将某个实体或数据对象表示为一个紧密的整体,即使该对象包含多个字段也是如此。在这些情况下,您可以使用 @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

数据存储结构,实际上在user表里面增加了列,去存储嵌套对象,会忽略嵌套对象的主键。

2、定义一对一关系

比如一个人有一个身份证

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

如果需要查询人和身份证一起查询出来,需要建立一个新的类,对2个实体进行关联。注意@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
}

如果要一起插入,好像是不行的,我用这个关联关系的类,直接用@Insert,是会报错的。目前只能2个表分开单独插入 例如

     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
)

@Dao
interface UserDao {
    @Transaction
    @Query("SELECT *FROM User")
    fun getUsersAndBooks():List
}

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

定义多对多

一首歌可以在多个播放列表,一个播放列表可以有多首歌
多对多关系与其他关系类型均不同的一点在于,子实体中通常不存在对父实体的引用。因此,需要创建第三个类来表示两个实体之间的关联实体(即交叉引用表)。交叉引用表中必须包含表中表示的多对多关系中每个实体的主键列。

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

查询播放列表和每个播放列表所含歌曲的列表,创建一个新的数据类,其中包含单个 Playlist 对象,以及该播放列表所包含的所有 Song 对象的列表

查询歌曲和每首歌曲所在播放列表的列表,创建一个新的数据类,其中包含单个 Song 对象,以及包含该歌曲的所有 Playlist 对象的列表。

两种情况下,都可以通过以下方法在实体之间建立关系:在上述每个类中的 Relation注释中使用 associateBy 属性来确定提供 Playlist 实体与 Song 实体之间关系的交叉引用实体。不用网上说的entity 那样只能查出一个数据

data class PlaylistWithSongs(
    @Embedded val playlist: PlayList,
    @Relation(
        parentColumn = "playlistId",
        entityColumn = "songId",
        associateBy = Junction(PlaylistSongCrossRef::class)

    )
    val songs: List
)

data class SongWithPlaylists(
    @Embedded val song: Song,
    @Relation(
        parentColumn = "songId",
        entityColumn = "playlistId",
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val playlists: List
)


测试

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


    @Insert
    fun addPlayList(playList: PlayList):Long
}

预填充数据库

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

数据库迁移

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 引用复杂数据


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
}

数据库里面存储的就是long

image.png

你可能感兴趣的:(Jetpack学习(五) Room存储)