一、Room数据库的大三组件
Database:即数据库。扩展了RoomDatabase的抽象类。可以通过Room获得它的一个实例。databaseBuilder或Room.inMemoryDatabaseBuilder。
Dao:数据访问对象,是Room的主要组件,负责定义访问数据库的方法。
Entity:实体类,代表一个表结构。
二、实现原理
在数据库DataBase中想要访问数据库的操作,需要获取操作Dao的对象,而Dao会给DataBase返回一个entity的对象。当我们用Room数据库进行存储时,其实存储的并非要存储的具体对象,而是对象对应的一些信息。因此,我们引入Repository仓库类用于操作Room,而我们给外部暴露的是viewModel类,因此,viewModel需要持有Repository对象。此外,Repository与viewModel之间也需要context传递上下文信息。
三、优点
- 针对 SQL 查询的编译时验证
- 可最大限度减少重复和容易出错的样板代码的方便注解
- 简化了数据库迁移路径
四、使用方式(以创建一个相册数据库为例)
- 声明依赖项
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
- 创建相册表实体类Entity
import androidx.room.Entity
import androidx.room.PrimaryKey
const val ALBUM_TYPE_IMAGE = 0
const val ALBUM_TYPE_VIDEO = 1
//默认表名就是类名
//@Entity(tableName = "album_table")
@Entity
data class Album (
@PrimaryKey(autoGenerate = true)//主键自增长取消,因为是string类型
val id:Int,
var albumName:String,
//@ColumnInfo(name ="cover_url") 表名和属性名的转换
var coverUrl:String,
var number:Int,
val type:Int = ALBUM_TYPE_IMAGE
)
//缩略图表
@Entity
data class ThumbImage(
@PrimaryKey(autoGenerate = false)
val imageName:String,
//外键
val albumId: Int
)
- 创建Dao
import androidx.room.*
import java.util.concurrent.Flow
@Dao
interface AlbumDao {
//创建相册 数据库操作都耗时suspend
//@Insert(onConflict = OnConflictStrategy.)解决冲突
@Insert
fun insertAlbum(album: Album)
//删除一个相册
@Delete
fun deleteAlbum(album: Album)
//删除多个相册 可变数组
@Delete
fun deleteAlbums(vararg album: Album)
//更新相册
@Update
fun updateAlbum(album: Album)
//查询所有相册信息
@Query("select * from Album where type = :type")
fun getAllAlbumsWithType(type:Int):kotlinx.coroutines.flow.Flow<List<Album>>
/**------------------------相片--------------------------**/
//插入一张图
@Insert
fun insertImage(thumbImage: ThumbImage)
//插入多张图
@Insert
fun insertImages(vararg thumbImage: ThumbImage)
//删除一张图
@Delete
fun deleteImage(thumbImage: ThumbImage)
//删除多张图
@Delete
fun deleteImages(vararg thumbImage: ThumbImage)
}
- 创建数据库DataBase
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(
entities = [Album::class,ThumbImage::class],
version = 1,
exportSchema = false //是否导出
)
abstract class AlbumDatabase:RoomDatabase() {
//获取一系列数据库访问的接口实现类
abstract fun albumDao():AlbumDao
//访问静态属性,提供单例对象
//有就返回,没有就加锁再返回
companion object{
private var INSTANCE:AlbumDatabase ? = null
fun getInstance(context: Context):AlbumDatabase{
if (INSTANCE != null){
return INSTANCE!!
}
synchronized(this){
if (INSTANCE == null){
INSTANCE = Room.databaseBuilder(
context,
AlbumDatabase::class.java,
"album_db"
).build()
}
return INSTANCE!!
}
}
}
}
5.创建Repository给viewModel提供访问方法
import android.content.Context
import kotlinx.coroutines.flow.Flow
class Repository(context: Context) {
private var albumDao:AlbumDao
init {
albumDao = AlbumDatabase.getInstance(context).albumDao()
}
//加载相册
suspend fun loadAlbumWithType(type:Int): Flow<List<Album>> {
return albumDao.getAllAlbumsWithType(type)
}
//插入相册
suspend fun addAlbum(album: Album){
albumDao.insertAlbum(album)
}
}
- viewModel持有Repository,给外部提供操作方法
package com.example.privatealbum.db
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.example.privatealbum.DEFAULT_COVER_URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
//与viewModel的区别是强制传一个参数,拥有application即拥有context
class SharedViewModel(application: Application)
:AndroidViewModel(application){
//保存所有相册信息
var imageAlbumList = MutableLiveData<List<Album>>(emptyList())
var videoAlbumList = MutableLiveData<List<Album>>(emptyList())
//仓库对象
val repository = Repository(application.applicationContext)
//保存当前添加相册的类型
var type = ALBUM_TYPE_IMAGE
//相册删除按钮默认不显示 有内容显示
var shouldShowDeleteInAlbum = MutableLiveData(false)
private var deleteAlbumList = arrayListOf<Album>()
//添加需要删除的相册
fun addAlbumToDeleteList(album: Album){
deleteAlbumList.add(album)
shouldShowDeleteInAlbum.postValue(true)
}
fun deleteAlbumFromDeleteList(album: Album){
deleteAlbumList.remove(album)
shouldShowDeleteInAlbum.postValue(deleteAlbumList.size > 0)
}
//获取相册
fun loadAlbumsWithType(albumType:Int){
viewModelScope.launch(Dispatchers.IO) {
val result = repository.loadAlbumWithType(albumType)
result.collectLatest {
if (albumType == ALBUM_TYPE_IMAGE){
imageAlbumList.postValue(it)
}else{
videoAlbumList.postValue(it)
}
}
}
}
//插入相册
fun addAlbum(name:String,type: Int){
viewModelScope.launch(Dispatchers.IO) {
val album = Album(
0,
name,
getApplication<Application>().DEFAULT_COVER_URL,
0,
type = type
)
repository.addAlbum(album)
}
}
}
五、注意事项
- Room 在创建数据库时,不需要手动编写 SQL 语句创建表,它会根据关联的数据实体类创建对应的表。在定义数据库时,通过 @Database 注解的 entities 参数指定数据库中关联的数据实体类(即所包含的数据表)。
- 数据库关联的数据实体类必须是 @Entity 注解标注的;
- 数据库新增数据表时,需要新建数据实体类,并将实体类添加到数据库定义的关联实体类列表中;
- 数据库只在首次创建数据库时,根据定义的关联数据库实体类创建表,无需编写 SQL 语句;
- 应用在新版本添加表(或更改表结构),需要进行数据库迁移(也就是常说的数据库升级),否则数据库不会创建新表(或更改表结构)。