Android JetPack Room
JetPack Room
- 1. Room 的优势
- 2. 主要组件
- 3. 简单使用介绍
- 3.1 导包
- 3.2 User实体类,UserDao接口,UserDataBase
- 4. 数据库的升级
- 4.1 直接升级
- 4.2 Migration方式,手动迁移
- 4.3 autoMigrations方法,自动迁移
- 5. exportSchema数据库升级测试记录
- 6. 监听表数据变化
- 6.1 LiveData方式
- 6.2 Flow流的方式
- 6.3 Rxjava的方式
- 7. 引用复杂数据
- 8. 预填充 Room 数据库
- 8.1 从应用资源中填充
- 8.2 从文件系统预填充
- 示例代码
1. Room 的优势
Room是JetPack的一种关系型数据库,Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
- 针对 SQL 查询的编译时验证。
- 可最大限度减少重复和容易出错的样板代码的方便注解。
- 简化了数据库迁移路径。
2. 主要组件
Room 包含三个主要组件:
- Entity:实体类,对应的是数据库的一张表结构,使用注解@Entity标记
- Dao:包含访问一系列访问数据库的方法,使用注解@Dao标记
- DataBase:数据库持有者,作为与应用持久化相关数据的底层连接的主要接入点。使用注解@Database标记,另外需要满足以下条件:定义的类必须继承于RoomDatabase的抽象类,在注解中需要定义与数据库相关联的实体类列表。包含一个没有参数的抽象方法并且返回一个Dao对象。
3. 简单使用介绍
3.1 导包
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"implementation 'androidx.core:core-ktx:1.6.0'implementation 'androidx.appcompat:appcompat:1.3.1'implementation 'com.google.android.material:material:1.4.0'implementation 'androidx.constraintlayout:constraintlayout:2.1.2'testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.3'androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'//room包implementation "androidx.room:room-runtime:2.4.0-alpha04"kapt "androidx.room:room-compiler:2.4.0-alpha04"testImplementation "androidx.room:room-testing:2.4.0-alpha04"//room使用协程implementation "androidx.room:room-ktx:2.2.4"//协程implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"//lifecycleimplementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"//工具库implementation "com.blankj:utilcodex:1.29.0"//gsonimplementation "com.google.code.gson:gson:2.8.7"
3.2 User实体类,UserDao接口,UserDataBase
User实体类定义:
@Entity(tableName = "user")//表名
class User {@Ignore //忽略的构造方法constructor(id: Int) {this.id = id}@Ignoreconstructor(firstName: String?, age: Int) {this.firstName = firstNamethis.age = age}constructor(id: Int, firstName: String?, age: Int) {this.id = idthis.firstName = firstNamethis.age = age}@PrimaryKey(autoGenerate = true)//自增长的主键@ColumnInfo(name = "id")var id: Int = 0//typeAffinity,指定类型,name指定行名称@ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)var firstName: String? = null@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)var age: Int = 0}
UserDao操作接口类
@Dao //注解Dao
interface UserDao {@Insert(onConflict = OnConflictStrategy.REPLACE)fun insertUsers(vararg users: User)//增@Deletefun deleteUser(user: User)//删除@Updatefun updateUser(user: User)//修改@Query("SELECT * FROM user")suspend fun getAllUser(): List<User>//添加suspend关键字,异步操作@Query("SELECT * FROM user")fun getAllUserFlow(): Flow<List<User>>//表数据变化时,返回Flow@Query("SELECT * FROM user")fun getAllUserLiveData(): LiveData<List<User>?>//表数据变化时,返回一个LiveData}
UserDataBase类
@Database(entities = [User::class],//指定表version = 1,//版本exportSchema = false//版本升级是否记录到本地,暂时为false
)
abstract class UserDataBase : RoomDatabase() {companion object {private var INSTANCE: UserDataBase? = null//初始化,这里要采用单例,不然会有坑(表数据变化时,可能会监听不了)fun init(context: Context) {if (INSTANCE == null) {synchronized(UserDataBase::class.java) {if (INSTANCE == null) {create(context)}}}}private fun create(context: Context) {INSTANCE =Room.databaseBuilder(context, UserDataBase::class.java, "user_db").allowMainThreadQueries()//可以在主线程执行查询,不建议这么做.fallbackToDestructiveMigration()//数据库改变时,强制升级时,不报错.build()}fun getDB(): UserDataBase {return INSTANCE ?: throw NullPointerException("UserDataBase 未初始化")}}//增加抽象方法,Room框架会自动实现这个方法abstract fun getUserDao(): UserDao}
增删改查操作:
//在Application中实例化UserDataBase
UserDataBase.init(this)//insert,插入三个数据val user1 = User("小明", 12)val user2 = User("小红", 18)val user3 = User("小清", 20)UserDataBase.getDB().getUserDao().insertUsers(user1, user2, user3)//删除id为3的Userval user = User(3)UserDataBase.getDB().getUserDao().deleteUser(user)//修改,将第一个数据修改名称与ageval user = User(1, "大明明3333", 15)UserDataBase.getDB().getUserDao().updateUser(user)//查询flow {//用Flow查询emit(UserDataBase.getDB().getUserDao().getAllUser())}.flowOn(Dispatchers.IO).onEach {//数据更新mAdapter.updateData(it.toMutableList())}.launchIn(lifecycleScope)
可以看到,操作非常简单,代码简洁清晰。
4. 数据库的升级
比如上面的User表,想在表中增加一行city,String类型怎么办?
4.1 直接升级
修改User的属性
@Entity(tableName = "user")
class User {@Ignoreconstructor(id: Int) {this.id = id}@Ignoreconstructor(firstName: String?, age: Int) {this.firstName = firstNamethis.age = age}constructor(id: Int, firstName: String?, age: Int) {this.id = idthis.firstName = firstNamethis.age = age}@PrimaryKey(autoGenerate = true)@ColumnInfo(name = "id")var id: Int = 0@ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)var firstName: String? = null@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)var age: Int = 0//增加城市这一行var city: String? = null}
将UserDataBase的version修改为2,
@Database(entities = [User::class],version = 2,//版本修改为2exportSchema = false
)
abstract class UserDataBase : RoomDatabase() {...//同时fallbackToDestructiveMigration一定要加上Room.databaseBuilder(context, UserDataBase::class.java, "user_db".allowMainThreadQueries().fallbackToDestructiveMigration().build()
这种方式好处是暴力快速,坏处是会清除以前数据库中的数据,不建议用这种方式。
4.2 Migration方式,手动迁移
//自定义版本1到2要修改的地方
private val Migration_1_2 = object : Migration(1, 2) {override fun migrate(database: SupportSQLiteDatabase) {//这里加入了一列database.execSQL("ALTER TABLE user ADD COLUMN city TEXT")}}//版本1到2添加addMigrationsINSTANCE = Room.databaseBuilder(context, UserDataBase::class.java, "user_db").addMigrations(Migration_1_2)//加上版本的改变.allowMainThreadQueries().fallbackToDestructiveMigration().build()
这种方式好处是会保留以前的数据,不好的地方是要自己写sql语句,有些sql语句比较复杂,而且很容易写错。那有没有更好的方法呢?
4.3 autoMigrations方法,自动迁移
注意:Room 在 2.4.0-alpha01 及更高版本中支持自动迁移。如果您的应用使用的是较低版本的 Room,则必须[手动定义迁移]。
@Database(version = 2,entities = [User::class],autoMigrations = [AutoMigration (from = 1, to = 2)//在上面的数据库修改中,只需要这一句,就可将表增加一列city,并保留以前的数据]
)
abstract class UserDataBase : RoomDatabase() {...
}
注意:Room 自动迁移依赖于为旧版和新版数据库生成的数据库架构。如果exportSchema设为false
,或者如果您尚未使用新版本号编译数据库,自动迁移将会失败。
5. exportSchema数据库升级测试记录
@Database(entities = [User::class],version = 5,exportSchema = true,//将此设置为trueautoMigrations = [ AutoMigration(from = 1, to = 2),AutoMigration(from = 2, to = 3),AutoMigration(from = 3, to = 4), AutoMigration(from = 4, to = 5)]
)
abstract class UserDataBase : RoomDatabase() {...
}
build.gradle中的defaultConfig设置
defaultConfig {applicationId "com.hjq.room"minSdkVersion 16targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"//Room版本升级记录javaCompileOptions {annotationProcessorOptions {arguments += ["room.schemaLocation":"$projectDir/schemas".toString()]//记录保存的地方}}}
运行程序后,会在项目目录下多一个schemas文件夹,
可以记录下来数据库版本升级的修改,方便维护与测试。
例如1.json如下:
{"formatVersion": 1,"database": {"version": 1,"identityHash": "51f493557826d6594d5cb9ea8939e231","entities": [{"tableName": "user","createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_name` TEXT, `age` INTEGER NOT NULL)","fields": [{"fieldPath": "id","columnName": "id","affinity": "INTEGER","notNull": true},{"fieldPath": "firstName","columnName": "first_name","affinity": "TEXT","notNull": false},{"fieldPath": "age","columnName": "age","affinity": "INTEGER","notNull": true}],"primaryKey": {"columnNames": ["id"],"autoGenerate": true},"indices": [],"foreignKeys": []}],"views": [],"setupQueries": ["CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)","INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '51f493557826d6594d5cb9ea8939e231')"]}
}
6. 监听表数据变化
6.1 LiveData方式
//UserDao增加一个接口
@Query("SELECT * FROM user")fun getAllUserLiveData(): LiveData<List<User>?>
//LiveData监听数据改变,当表执行了Insert,Update,Delete操作后,会打印data changemViewModel.getUserLiveData().observe(this) { t ->LogUtils.d("data change")if (!t.isNullOrEmpty()) {mAdapter.updateData(t.toMutableList()) }}
6.2 Flow流的方式
//UserDao增加一个接口@Query("SELECT * FROM user")fun getAllUserFlow(): Flow<List<User>>
//Flow监听表数据改变,当表执行了Insert,Update,Delete操作后,会打印data changemViewModel.getUserFlow().onEach {LogUtils.d("data change")mAdapter.updateData(it.toMutableList())}.launchIn(lifecycleScope)
6.3 Rxjava的方式
感兴趣的同学可以查看官网。
7. 引用复杂数据
比如在上面的User中要引入一个UserPosition数据
class UserPosition {//经度var latitude: Double = 0.0//纬度var longitude: Double = 0.0//海拔var altitude: Double = 0.0
}
//在User类中增加一行,这时需要指定类型转换类,实际是userPosition,但存在数据库中确是其他格式,
//为什么要做成这样呢?为了数据安全,避免表结构冲突@ColumnInfo(name = "user_position")var userPosition: UserPosition? = null
//增加转换类,存储时将UserPosition转成Json,获取时则将Json转成UserPosition
object UserPositionConverter {@TypeConverterfun objectToString(value: String?): UserPosition? {try {return Gson().fromJson(value, UserPosition::class.java)} catch (e: Exception) {LogUtils.d("e===$e")e.printStackTrace()}return null}@TypeConverterfun stringToObject(deviceEventsBean: UserPosition?): String {try {return Gson().toJson(deviceEventsBean)} catch (e: Exception) {LogUtils.d("e===$e")e.printStackTrace()}return ""}
}
@Entity(tableName = "user")
@TypeConverters(UserPositionConverter::class)//添加转换类
class User {@Ignoreconstructor(id: Int) {this.id = id}@Ignoreconstructor(firstName: String?, age: Int) {this.firstName = firstNamethis.age = age}constructor(id: Int, firstName: String?, age: Int) {this.id = idthis.firstName = firstNamethis.age = age}@PrimaryKey(autoGenerate = true)@ColumnInfo(name = "id")var id: Int = 0@ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)var firstName: String? = null@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)var age: Int = 0//增加城市这一行var city: String? = null@ColumnInfo(name = "user_position")var userPosition: UserPosition? = null}
8. 预填充 Room 数据库
有时,您可能希望应用启动时数据库中就已经加载了一组特定的数据。这称为预填充数据库。 在 Room 2.2.0 及更高版本中,您可以使用 API 方法在初始化时用设备文件系统中预封装的数据库文件中的内容预填充 Room 数据库。
注意:[内存中 Room 数据库]不支持使用 [createFromAsset()
] 或 [createFromFile()
]预填充数据库。
8.1 从应用资源中填充
如需从位于应用 assets/
目录中的任意位置的预封装数据库文件预填充 Room 数据库,请先从 RoomDatabase.Builder
对象调用 createFromAsset()
方法,然后再调用 build()
:
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db").createFromAsset("database/myapp.db").build()
8.2 从文件系统预填充
如需从位于设备文件系统任意位置(应用的 assets/
目录除外)的预封装数据库文件预填充 Room 数据库,请先从 RoomDatabase.Builder
对象调用 createFromFile()
方法,然后再调用 build()
:
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db").createFromFile(File("mypath")).build()
示例代码
代码
Android JetPack Room相关推荐
- Android Jetpack架构组件之 Room(使用、源码篇)
2019独角兽企业重金招聘Python工程师标准>>> 1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发 ...
- Android Jetpack组件之Hilt使用
前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. And ...
- Android Jetpack组件App Startup简析
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之WorkManger使用介绍
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之Navigation使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之 Room使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之 Paging使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之 LiveData使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之ViewModel使用
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack 组件之 Lifecycle源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
最新文章
- Codeforces Round #506 (Div. 3)
- cocos2d-x注意事项(十)Lua发展飞机战争-4-创建主角
- 题目1183:守形数
- c语言无符号数除法,[求助]关于双字节无符号数除法
- 【转】selector函数指针回调机制
- 前端学习(3233):高阶函数函数柯里化案例
- Python中if判断语句在只有一个break子句时可以写在一行
- Bailian2723 不吉利日期(POJ NOI0113-02)【日期计算】
- centos一键安装包无法创建vhost
- python开发自动化创建一个任务下发到手机_django2 +requests+ddt+unittest+HTMLestRunner接口自动化测试平台...
- uva 436(floyd变形)
- 深入理解JVM虚拟机读书笔记——垃圾回收算法
- matlab批量处理图片压缩
- html页面中中文转英文插件,iText 7 的htmlToPdf插件支持转换中文
- 用c语言编写文曲星小游戏,这是文曲星里的小游戏,本人用c语言将其编出并添加破纪录功能。...
- dp什么意思java_%~dp0是什么意思
- 一度智信:新开的电商店铺销量低?如何快速提升
- 管理学一些常用定律(转)
- 操作系统课程设计——时间片轮转算法模拟
- ASRT语音识别项目