一.让项目支持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用于在检索/查看数据时强制实施关系结构。
所以,如果是查询使用relation和embedded
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来判断