jetPack组件必学#Room

44 阅读6分钟

jetPack组件必学#Lifecycle

jetPack组件必学#LiveData&ViewMode

jetPack组件必学#DataBinding

jetPack组件必学#Dagger2&Hilt

jetPack组件必学#Navigation

jetPack组件必学#Room

一.让项目支持room

1.配置插件、依赖包

plugins {
    id 'kotlin-kapt'
}
dependencies {
    def room_version = "2.4.2"
    implementation"androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
}

二.基本使用

1.entity代码编写

//设置表名
@Entity(tableName = "user")
data class User(@PrimaryKey var uid:String,@ColumnInfo(name="nick_name")val nickName:String)

2.dao层封装

提供数据库访问抽象接口

    @Dao
    interface  UserDao {

        @Delete
        fun delete(user: User)

        @Update
        fun update(user: User)

        @Insert
        fun insert(user: User)

        @Query("SELECT * FROM user")
        fun queryAll():MutableList<User>
    }

3.数据库的生成


    @Database(entities = [User::class], version = 1, exportSchema = false)
    abstract class AppDatabase() : RoomDatabase() {

        abstract fun UserDao(): UserDao
        abstract fun RxUserDao(): RxUserDao

        companion object {
            @Volatile
            private var instance: AppDatabase? = null
            fun instance(context: Context) {
                if (instance == null) {
                    synchronized(AppDatabase::class.java) {
                        if (instance == null) {
                            instance=Room.databaseBuilder(context, AppDatabase::class.java, "app_data")
                                .allowMainThreadQueries()
                                .build()
                        }
                    }
                }
            }

            @Throws(IllegalStateException::class)
            fun instance(): AppDatabase {
                if (instance == null) {
                    throw IllegalStateException("please init AppDatabase")
                }
                return instance!!
            }

        }

    }

4.基本的crud

         create.setOnClickListener {
            AppDatabase.instance().UserDao()
                .insert(User("1", "tom"))
        }
        retrieve.setOnClickListener {
            AppDatabase.instance()
                .UserDao().queryAll().forEach {
                    println(it)
                }
        }
        update.setOnClickListener {
            AppDatabase.instance().UserDao()
                .update(User("1", "jerry"))
        }
        delete.setOnClickListener {
            AppDatabase.instance()
                .UserDao().delete(User("1", "jerry"))
        }

5.外键的一些设计

1.使用中间数据层(跨条件查询)

  • 当关系时1对1时,比如每个user对应的的用户信息info时
            @Entity(tableName = "user_info")
            data class Info(
                @PrimaryKey var id: String, @ColumnInfo(name = "age") val age: Int,
                @ColumnInfo(name = "user_id") val userId: String
            )

            @Entity(tableName = "user")
            data class User(@PrimaryKey var uid:String,@ColumnInfo(name="nick_name")val nickName:String)

            data class UserInfo(var userName:String, var age:Int)
            @Dao
            interface  UserInfoDao {
                /**
                 * select xxx as  xxx
                 * 查询到的数据作为别名输出 然后生成 UserInfo 对象
                 * 也就是查询到用户user.uid 和 user_info.user_id是一样的
                 * 每个人有一个用户信息,每个用户信息属于一个人
                 * 1:1 之后生成1个新的对象 并返回到列表中去
                 */
                @Query(
                    "SELECT user.nick_name As userName ,user_info.age As  age From user,user_info WHERE user.uid= user_info.user_id"
                )
                fun queryInfoByUser(): MutableList<UserInfo>
            }
            val users = mutableListOf<User>()
            val infos = mutableListOf<Info>()
            for (i in 1..9) {
                println("RelationActivity $i")
                val user = User("$i", "nickName_$i")
                users.add(user)
                val info = Info("$i", i + 2, user.uid)
                infos.add(info)
            }
            AppDatabase.instance()
                .UserDao().insertList(users).subscribe(object : CompletableObserver {
                    override fun onSubscribe(d: Disposable) {

                    }

                    override fun onComplete() {
                        println("RelationActivity insert users complete")
                    }

                    override fun onError(e: Throwable) {
                        println("RelationActivity insert users error ${e.message}")
                    }
                })
            AppDatabase.instance()
                .InfoDao().insertList(infos).subscribe(object : CompletableObserver {
                    override fun onSubscribe(d: Disposable) {

                    }

                    override fun onComplete() {
                        println("RelationActivity insert infos complete")
                    }

                    override fun onError(e: Throwable) {
                        println("RelationActivity insert infos error ${e.message}")
                    }
                })

            val infoUsers = AppDatabase.instance().UserInfoDao().queryInfoByUser()

            println("RelationActivity info users $infoUsers")
            infoUsers.forEach {
                println("RelationActivity :$it")
            }
  • 当关系时1对N时,比如每个user可以拥有多本书
    @Dao
    interface BookDao {

        @Delete
        fun delete(book: Book)

        @Update
        fun update(book: Book)

        @Insert
        fun insert(book: Book)


        @Insert
        fun insertList(books: MutableList<Book>):Completable

        @Query("SELECT * FROM book")
        fun queryAll():MutableList<Book>

    }

    //主键为long int 这种类型穿空过来,设置为自增长
    //设置表名
    @Entity(tableName = "book")
    data class Book(
        @PrimaryKey(autoGenerate = true) var id: Long? ,
        @ColumnInfo(name = "book_name") val book_name: String,
        @ColumnInfo(name = "user_id") val userId: String
    )

    /**
     *
     * 1:N
     * select table_a JOIN table_b  ON condition
     * JOIN连接表
     * ON 连接条件
     */
    @Query(
        "SELECT * FROM user JOIN book ON user.uid = book.user_id"
    )
    fun queryBookByUser(): Map<User, MutableList<Book>>

 val users = mutableListOf<User>()
            val books = mutableListOf<Book>()
            for (i in 11..20) {
                println("RelationActivity i $i")
                val user = User("$i", "nickName_$i")
                users.add(user)
                val nextInt = Random.nextInt(5)
                println("RelationActivity nextInt:$nextInt")
                for (j in 1..nextInt) {
                    println("RelationActivity j $j")
                    val book = Book(null,"bookName$j", "$i")
                    books.add(book)
                }

            }

            AppDatabase.instance()
                .UserDao().insertList(users).subscribe(object : CompletableObserver {
                    override fun onSubscribe(d: Disposable) {

                    }

                    override fun onComplete() {
                        println("RelationActivity insert users N complete")
                    }

                    override fun onError(e: Throwable) {
                        println("RelationActivity insert users N error ${e.message}")
                    }
                })
            AppDatabase.instance()
                .BookDao().insertList(books).subscribe(object : CompletableObserver {
                    override fun onSubscribe(d: Disposable) {

                    }

                    override fun onComplete() {
                        println("RelationActivity insert books complete")
                    }

                    override fun onError(e: Throwable) {
                        println("RelationActivity insert books error ${e.message}")
                    }
                })
            val queryBookByUser = AppDatabase.instance().UserRelationDao()
                .queryBookByUser()
            queryBookByUser.forEach { (t, u) ->
                println("RelationActivity user----->: $t")
                println("RelationActivity book.size----->: ${u.size}")
                u.forEach {
                    println("RelationActivity book: $it")
                }

            }

2.内嵌对象

1.embedded注解的使用

逻辑组合体,还可以重复套娃过程。但是如果字段有重名,则需要注意避免重复;内嵌类可以不是数据实体类(也就是说被嵌套的类可以无需 @Entity 注解标注)

//使用embedded这个注解将info作为嵌套对象注入到UserInfo中去,这个对象中所有的字段会注入到user_info_embed这个表中
@Entity(tableName = "user_info_embed")
data class UserInfoEmbedded(@PrimaryKey var uid:String,@ColumnInfo(name="nick_name")val nickName:String,@Embedded val info: Info)

//设置表名 主要如果可为空 需要设置int/long 不可以为string
@Entity(tableName = "user_info")
data class Info(
    @PrimaryKey(autoGenerate = true) var id: Long, @ColumnInfo(name = "age") val age: Int,
    @ColumnInfo(name = "user_id") val userId: String
)

val info=Info(19,9,"19")
            AppDatabase.instance()
                .UserRelationDao().insertEmbedded(UserInfoEmbedded("19","tony",info)).subscribe(object : CompletableObserver {
                    override fun onSubscribe(d: Disposable) {

                    }

                    override fun onComplete() {
                        println("RelationActivity insert embedded complete")
                    }

                    override fun onError(e: Throwable) {
                        println("RelationActivity insert embedded error ${e.message}")
                    }
                })
2.embedded和relation注解

embedded可以在表中嵌入另外一个对象从而变成一个新的表,而relation则可以在组合类中标识父子键,比如user和info的关系,我们除了用上述的中间数据层,还可以通过releation。

     // 1:1的关系 用一个新的数据类
    //1.注意其中一个实体必须包含对另一个实体的主键的引用。
    data class UserAndInfo(
        @Embedded val user: User,
        @Relation(
            parentColumn = "uid",
            entityColumn = "user_id"
        )
        val info: Info
    )

    //2.该方法需要 Room 运行两次查询,因此应向该方法添加 @Transaction (事务)注解,以确保整个操作以原子方式执行
    @Transaction
    @Query("SELECT * FROM user")
    fun getUsersAndInfo(): MutableList<UserAndInfo>
-----------------------------------------------------------------------------------------------------------
    //1:N的关系 构建一个新的数据类
    data class UserAndBooks(
        @Embedded val user: User,
        @Relation(
            parentColumn = "uid",
            entityColumn = "user_id"
        )
        val books: MutableList<Book>
    )

    //2.该方法需要 Room 运行两次查询,因此应向该方法添加 @Transaction (事务)注解,以确保整个操作以原子方式执行
    @Transaction
    @Query("SELECT * FROM user")
    fun getUsersAndBooks(): MutableList<UserAndBooks>
-----------------------------------------------------------------------------------------------------------

    //1.N:N通常没有主键引用关系,比如音乐中有喜爱列表,有收藏,所以音乐可能属于喜爱,可能属于收藏
    @Entity
    data class Playlist(
        @PrimaryKey val playlistId: Long,
        val playlistName: String
    )

    @Entity
    data class Song(
        @PrimaryKey val songId: Long,
        val songName: String,
        val artist: String
    )
    //2.那就需要构建第三方交叉关系表
// 交叉关系表的主键列名必须跟对应的数据实体类主键列名一致,如果变量名跟列名不一致,可以使用 @columnInfo 注解指定列名
    @Entity(primaryKeys = ["playlistId", "songId"])
    data class PlaylistSongCrossRef(
         // Playlist 实体类主键列名是playlistId, Song 实体类主键列名是songId
        val playlistId: Long,
        val songId: Long
    )
    //3.还需要定义数据类
    // 查询播放列表的歌曲(查询 Playlist 表, 联合获取 Song 信息)
    data class PlaylistWithSongs(
        @Embedded val playlist: Playlist,
        @Relation(
             parentColumn = "playlistId",
             entityColumn = "songId",
             associateBy = Junction(PlaylistSongCrossRef::class)
        )
        val songs: List<Song>
    )
    // 查询歌曲包含在哪些播放列表当中(查询 Song 表, 联合获取 Playlist 信息)
    data class SongWithPlaylists(
        @Embedded val song: Song,
        @Relation(
             parentColumn = "songId",
             entityColumn = "playlistId",
             associateBy = Junction(PlaylistSongCrossRef::class)
        )
        val playlists: List<Playlist>
    )
    //4.定义Dao层
    @Transaction
    @Query("SELECT * FROM Playlist")
    fun getPlaylistsWithSongs(): List<PlaylistWithSongs>
    
    @Transaction
    @Query("SELECT * FROM Song")
    fun getSongsWithPlaylists(): List<SongWithPlaylists>

-----------------------------------------------------------------------------------------------------------
    




    

3.外键约束

room外键约束是通过ForeignKey注解来关联两个表之间的关系


//book中的userId对应user中的id user删除,对应的book也会被删除
//CASCADE:User删除时对应Book一同删除; 更新时,关联的字段一同更新
//NO_ACTION:User删除时不做任何响应
//RESTRICT:禁止User的删除/更新。当User删除或更新时,Sqlite会立马报错。
//SET_NULL:当User删除时, Book中的userId会设为NULL
//SET_DEFAULT:与SET_NULL类似,当User删除时,Book中的userId会设为默认值

@Entity(foreignKeys = @ForeignKey(entity = User.class,
                                  parentColumns = "id",
                                  childColumns = "user_id",
                                  onUpdate = ForeignKey.CASCADE,
                                   onDelete = ForeignKey.CASCADE))
data class Book {
    @PrimaryKey
    public int bookId;
    public String title;
    @ColumnInfo(name = "user_id")
    public int userId;
    
}

@Entity
data class Artist(
    @PrimaryKey val id: Long,
    val name: String
)

@Entity(
    foreignKeys = [ForeignKey(
        entity = Artist::class,
        parentColumns = arrayOf("id"),
        childColumns = arrayOf("artistId"),
        onUpdate = ForeignKey.CASCADE,
        onDelete = ForeignKey.CASCADE
    )]
)
data class Album(
    @PrimaryKey val albumId: Long,
    val title: String,
    val artistId: Long
)
注意:@ForeignKey用于在插入/修改数据时强制实施关系结构
     @Relation用于在检索/查看数据时强制实施关系结构。
所以,如果是查询使用relationembedded

6.数据库升级方案

正常升级是1-2,2-3这种,使用的Migration做升级,然后去写sql语句

上面是正常升级的代码,但是如果往后升可能是1-2,2-3一直添加,但是如果我们忘了写Migration,我们可以通过

,他可以帮我们回退数据库到上一个版本,但是可能会数据丢失

//1.修改版本号    
@Database(
        entities = [User::class, Info::class, Book::class, UserInfoEmbedded::class],
        version =2,
        exportSchema = false
    )  
//2.写一个Migration
class Migration_1_2 : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("ALTER TABLE user ADD COLUMN sex Integer NOT NULL DEFAULT 1 ")
        }
    }
//3.添加升级策略
 Room.databaseBuilder(context, AppDatabase::class.java, "app_data.db")
                                .allowMainThreadQueries()
                                .addMigrations(Migration_1_2())
                                .build()
      Room.databaseBuilder(context, AppDatabase::class.java, "app_data.db")
                                .allowMainThreadQueries()
                                .addMigrations(Migration_1_2())
                                .fallbackToDestructiveMigration()
                                .build()

7.销毁、重建策略与数据库的预制

当我们升级数据库时,需要修改字段的类型时,我们就需要创建一个temp临时表,然后把需要修改字段的类型写入到这个临时表中,然后删除这个表,在将临时表重命名。

class Migration_2_3 : Migration(2, 3) {
   override fun migrate(database: SupportSQLiteDatabase) {
       database.execSQL("CREATE TABLE user_temp ('uid' Text PRIMARY KEY NOT NULL, 'nick_name' TEXT,'sex' Text DEFAULT 'M')")
       database.execSQL("INSERT INTO user_temp(uid,nick_name,sex) SELECT uid,nick_name,sex FROM user")
       database.execSQL("DROP TABLE user")
       database.execSQL("ALTER TABLE user_temp RENAME TO user")
   }
}

Room.databaseBuilder(context, AppDatabase::class.java, "app_data.db")
                               .allowMainThreadQueries()
                               .addMigrations(Migration_1_2())
                               .fallbackToDestructiveMigration()
                               .createFromAsset("xxx")//首次加载时预制进去的数据库
                               .build()

三.结合Rxjava及其坑点

1.使用步骤

1.配置gradle

2.让dao层返回rx类型的数据

3.single/Flowable/Maybe/completeable作为返回值的不同

   implementation "androidx.room:room-rxjava3:$room_version"
    @Delete
    fun delete(user: User): Completable

    @Update
    fun update(user: User): Completable

    @Insert
    fun insert(user: User): Completable

    @Query("SELECT * FROM user")
    fun queryAllObserver(): Observable<MutableList<User>>

    @Query("SELECT * FROM user")
    fun queryAllMay(): Maybe<MutableList<User>>


    @Query("SELECT * FROM user")
    fun queryAllLive(): LiveData<MutableList<User>>
参考:https://blog.csdn.net/wumeixinjiazu/article/details/124155165
参考:https://blog.csdn.net/chuyouyinghe/article/details/122812421
      *insert时 Single<Long>
     * Long返回都不行,只能使用Completable
     * Completable它不发射数据。只处理onComplete和onError,可以看成Rx的runnable

* @Query(“SELECT * FROM Users WHERE id = :userId”)
 * Flowable<User> getUserById(String userId);
 * 当数据库中没有该用户时,查询返回no rows,Flowable不发射数据,既没有onNext,也没有onError.
 * 当数据库中有一个用户时,Flowable将触发onNext.
 * 每次用户数据有更新,Flowable对象都会自动发出,允许你用最新的数据更新UI.
 * -----------------------------------
 * Single
 * @Query(“SELECT * FROM Users WHERE id = :userId”)
 * Single<User> getUserById(String userId);
 * 当数据库中没有该用户时,查询返回no rows,Single将触发onError(EmptyResultSetException.class).
 * 当数据库中有一个用户时,Single将触发onSuccess.
 * 当Single.onComplete被调用后user有更新,nothing happens,因为流已经完成了。
 * Maybe
 * @Query(“SELECT * FROM Users WHERE id = :userId”)
 * Maybe<User> getUserById(String userId);
 * 当数据库中没有该用户时,query 返回 no rows,Maybe complete.
 * 当数据库中有一个用户时,Maybe 会触发onSuccess,然后complete.
 * 如果在Maybe completed 之后user有更新,nothing happens.

2.Rxjava与liveData的相同与不同

Rxjava和LiveData在观察表后,只有有数据变化都有可能触发观察者,不过livedata是有生命周期的,如果rxjava想要有生命周期依赖,需要绑定rxlifecycle。

四.结合协程的room

待续 其实就是一个suspend关键字,然后在开启协程中调用,但是插入、删除、更新是否成功怎么判断呢?毕竟结合Rxjava还能有completable来判断