Android Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码。而这篇文章中的主角Room正好是Jetpack 的重要库之一,Room数据库在这近几年也是Google极力推荐使用的数据库。
说到这个主角,Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的用例是缓存相关数据。这样,当设备无法访问网络时,用户仍可在离线状态下浏览相应内容。设备重新连接到网络后,用户发起的所有内容更改都会同步到服务器。
在下面的学习中,我将以Kotlin的开发环境中进行,毕竟要迎合Google大佬官方开发语言的推荐,话不多说,我们直接展开说下这个数据库是怎么使用的。
想必原生的Android数据库SQLite打击都不陌生,而本篇文章的主角Room其实就只是对原生的SQLite API进行了一层封装,使得更加流畅的访问数据库。另外它和常规的ORM框架一样,通过添加编译期注解来进行表和字段的配置,其包含了三个主要组件:
1、Databasee:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点;使用 @Database 注释的类应满足以下条件:是扩展 RoomDatabase 的抽象类。在注释中添加与数据库关联的实体列表。包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 的实例。
2、Entity:表示数据库中的表,通过@Entity标识。
3、DAO:包含用于访问数据库的方法,通过@Dao标识。
Kotlin环境依赖如下:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android {
defaultConfig {
...
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
}
}
dependencies {
def room_version = "2.2.6"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
通过@Entity
注解标识实体类,其中tableName
表示明确表名,如果不明确表名数据库指定当前实体作为表名,@PrimaryKey
注解标识主键,是唯一的值,值不能为空,我这里默认指定为0,autoGenerate = true
表示主键自增; @ColumnInfo
注解标识表中列信息,name =
指定列中对应数据库字段名称,不指定时默认为自定义的参数名称:
@Entity(tableName = "student_info")
data class StudentEntity(
@ColumnInfo(name = "stu_name")
val name: String?,
@ColumnInfo(name = "stu_sex")
val sex: String?,
@ColumnInfo(name = "stu_skill")
val skill:String?,
@PrimaryKey(autoGenerate = true)
val id: Long = 0
)
创建数据库需要继承RoomDatabase的抽象类,并且通过@Database
标识这个类,@Database
中里面有几个参数可以展开说一下,entities
表示要添加进来的数据库表,这里是以数组的形式添加,后续有多个表可以用逗号隔开添加进来,version
表示这个数据库的版本,版本迁移时会使用到,exportSchema
表示导出为文件模式,默认为true,除了添加表映射的类以及和数据库版本外,还要添加exportSchema = false否则会报警告。这个类里面还定义了获取增删改查操作接口的方法StudentDAO,下面还涉及了版本迁移的方法,当数据库表Entity中某些字段增加或减少就需要调用数据库迁移的方法,此外,打比方原来的版本(version)是1,就要改成2,下一次字段变更时,版本以此类推的增加。想要保留数据的情况下做版本迁移就需要调用addMigrations()
方法,这个非常重要。
@Database(entities = [StudentEntity::class], version = 3, exportSchema = false)
abstract class StudentDB : RoomDatabase() {
abstract fun getStudentDao(): StudentDAO
companion object {
//版本迁移:2->3(这个是增加字段下的迁移)
private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE student_info ADD COLUMN stu_skill TEXT") //在数据库表student_info中添加了一个字段stu_skill
}
}
//版本迁移3->4(这个是删减字段下的迁移,过程较复杂),整个步骤如下:
private val MIGRATION_3_4: Migration = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
//1.在旧表的前提下先创建一个删减掉某个字段的新表(我这里包含字段:id,stu_name,stu_sex),去除stu_skill字段;
database.execSQL("CREATE TABLE student_info_temp (stu_name TEXT,stu_sex TEXT,id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL) ")
// 2.然后将旧表student_info的数据转移到新表student_info_temp中(旧表和新表的字段要保持一致)
database.execSQL("INSERT INTO student_info_temp (stu_name,stu_sex,id) SELECT stu_name,stu_sex,id FROM student_info")
// 3.将旧表student_info删除.
database.execSQL("DROP TABLE student_info")
//4.将新建的表名命名student_info_temp成旧表的名student_info
database.execSQL("ALTER TABLE student_info_temp RENAME TO student_info")
}
}
//双重检测锁单例初始化StudentDB
val INSATANCE: StudentDB by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
Room
.databaseBuilder(App.appContext(), StudentDB::class.java, "StudentAllInfo.db")
.addMigrations(MIGRATION_2_3)//这个方法是在保留数据的前提下做版本迁移,fallbackToDestructiveMigration()是将原有数据清空在做版本迁移
.build()
}
}
}
定义一个数据操作接口,使用@Dao
注解标识,Room中提供了@Insert
、@Delete
、@Update
、@Query
、@RawQuery
等注解,通过注解标识实现对应的增删改查的方法,函数中suspend
表示函数挂起,在协程中使用需要此关键字修饰,挂起函数挂起协程,并不会阻塞协程所在的线程:
@Dao
interface StudentDAO {
//插入多个数据
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(students: MutableList<StudentEntity>)
//展示所有数据
@Query("SELECT * FROM student_info")
fun queryAll(): LiveData<MutableList<StudentEntity>>
//删除表中所有数据
@Query("DELETE FROM student_info")
suspend fun deleteAll()
//通过ID修改数据
@Query("UPDATE student_info SET stu_name=:name,stu_sex=:sex,stu_skill=:skill WHERE id=:id")
suspend fun updateData(id: Long, name: String, sex: String, skill: String)
//根据Id删除数据
@Query("DELETE FROM STUDENT_INFO WHERE id=:id")
suspend fun deleteById(id: Long)
//根据姓名删除数据
@Query("DELETE FROM STUDENT_INFO WHERE stu_name=:name")
suspend fun deleteByName(name: String)
//根据性别删除数据
@Query("DELETE FROM STUDENT_INFO WHERE stu_sex=:sex")
suspend fun deleteBySex(sex: String)
}
这里插个题外话,本想在这个操作接口中使用Rxjava
实现对数据库增删改查的操作,但定义好方法,Build的时候出现一系列错误,通过查找文档,Google搜索问题未果,只能放弃(PS:哪位大兄弟如果有此问题解决方式或者文章可以麻烦推送给我)
,最终我这里使用LiveData
搭配Room一起使用,实现数据实时更新的监听效果,在操作数据时使用Kotlin的协程Coroutine对LiveData不太熟悉的可以自行查询官方文档。
初始化Database,这里我使用了Koin依赖注入,关于这个框架我下次出一个使用教程:
class App : Application(){
override fun onCreate() {
super.onCreate()
//初始化Koin依赖注入框架
initKoin()
}
//数据库管理
private val dbMode = module {
single { StudentDB.INSATANCE }//初始化Database
single { get<StudentDB>().getStudentDao() }//获取StudentDAO数据库操作类
}
private fun initKoin() {
startKoin {
androidContext(this@App)
modules(dbMode)
}
}
}
在Activity中使用协程lifecycleScope.launch
通过线程切换方式操作数据,实现与Rxjava相同的效果,launch
中提供了线程方式:Default
、Main
、Unconfined
、IO
,默认是在Main中执行,也就是主线程:
class RoomDataBaseLearnActivity : AppCompatActivity() {
//通过依赖注入方式获取数据库操作类
private val studentDao: StudentDAO by inject()
private var job: Job? = null
private var vb: ActivityRoomDataBaseLearnBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vb = ActivityRoomDataBaseLearnBinding.inflate(layoutInflater)
vb?.apply {
setContentView(root)
//插入多条数据
btnInsertAll.setOnClickListener {
insertMultiData()
}
//修改某条数据
btnUpdateData.setOnClickListener {
updateData()
}
//删除所有数据
btnDeleteAll.setOnClickListener {
deleteAllData()
}
//根据ID删除数据
btnDeleteByid.setOnClickListener {
delDataById()
}
//根据姓名删除数据
btnDeleteByname.setOnClickListener {
delDataByName()
}
//根据性别删除数据
btnDeleteBysex.setOnClickListener {
delDataBySex()
}
//实时查询(刷新)数据
refreshData()
}
}
private fun delDataBySex() {
job = lifecycleScope.launch(Dispatchers.IO) {
studentDao.deleteBySex("男")
loadThreadLog("根据性别删除")
}
}
private fun delDataByName() {
job = lifecycleScope.launch(Dispatchers.IO) {
studentDao.deleteByName("张三丰")
loadThreadLog("根据姓名删除")
}
}
private fun delDataById() {
job = lifecycleScope.launch(Dispatchers.IO) {
studentDao.deleteById(1)
studentDao.deleteById(2)
loadThreadLog("根据ID删除")
}
}
private fun refreshData() {
studentDao.queryAll().observe(this@RoomDataBaseLearnActivity, {
job = lifecycleScope.launch(Dispatchers.IO) {//子线程IO处理数据库耗时操作
loadThreadLog("查询刷新数据---->外")
val sb = StringBuilder()
it.forEach {
sb.append(it.id).append(" ").append(it.name).append(" ").append(it.sex).append(" ").append(it.skill)
.append("\n")
}
withContext(Dispatchers.Main) {//切换主线程显示UI
vb!!.tvRoom.text = sb.toString()
loadThreadLog("查询刷新数据---->内")
}
}
})
}
private fun deleteAllData() {
job = lifecycleScope.launch(Dispatchers.IO) {
studentDao.deleteAll()
loadThreadLog("删除数据")
}
}
private fun updateData() {
job = lifecycleScope.launch(Dispatchers.IO) {
studentDao.updateData(2, "武松", "男","力大无穷")
studentDao.updateData(8, "扈三娘", "女","彪悍")
studentDao.updateData(11, "姜子牙", "男","周易卜卦")
studentDao.updateData(13, "妲己", "女","迷惑")
loadThreadLog("修改数据")
}
}
private fun insertMultiData() {
job = lifecycleScope.launch(Dispatchers.IO) {
studentDao.insertAll(DataManager.studentData)
loadThreadLog("插入数据")
}
}
private fun loadThreadLog(method: String) {
Log.e(
"协程线程---->",
"协程执行$this ----> $method 线程id:${Thread.currentThread().id} 当前是否为主线程:${isMainThread()}"
)
}
override fun onDestroy() {
super.onDestroy()
if (vb != null)
vb = null
job?.cancel()
job = null
}
}
Room作为Google官方推荐使用的数据库,相比较某些优秀数据库框架来说,不用过于担心某天库会停止维护(参考GreenDao现在的状况),访问数据库非常流畅,并且提供了与常规的ORM框架一样,通过添加编译期注解来进行表和字段的配置,譬如@Database、@Dao、@Entity、@Query、@Insert、@Update、@Detele
等的注解,可以使用简单代码实现相比以前SQLite更复杂的代码的效果。总而言之, Room算得上是一个优秀的数据库,最后希望这篇文章不仅仅是自己作为学习者记录学习的过程,更希望能帮助刚入门这个数据库的人小试牛刀,最后附上自己的GitHub学习项目,这小项目旨在自己作为kotlin开发语言下展开对以前乃至现在热门的框架的学习,这段时间自己会不留余力去更新其他框架。
我的Github项目:KotlinAppFramework:此库针对Kotlin开发环境下对Android各大框架的简单使用,涉及到的框架有:Glide,Retrofit,Rxjava,ARouter,EventBus等,以促进基础学习的过程。