Room 数据库框架最全攻略
Room 是Google官方推出的Android Sqlite数据库处理框架,是子啊Sqlite上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。本文的目的旨在对于Room框架可以快速上手,内容分为两部分,第一部分为数据库的基本操作(增、删、改、查),第二部数据库的升级,加密
基本使用
基本工作的准备
- 引入Room依赖 (在Module的build.gradle文件中添加如下依赖)
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'//在此处引入kapt插件
}
...
dependencies {
...
def room_version = "2.2.6" // check latest version from docs
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
...
}
以上准备工作就做的差不多了,接下来开始建立数据库表
-
建表
本文已 User 表为例
@Entity data class User( @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") var lastName: String, @ColumnInfo(name = "age") val age: Int = 0, @PrimaryKey(autoGenerate = true) val uid: Int = 0 )
如上,一张User表就建好了,如果指定表名,默认是使用类名作为表明,如果要指定表名可以这样:
@Entity(tableName = "first_user") data class User( @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") var lastName: String, @ColumnInfo(name = "age") var age: Int = 0 @PrimaryKey(autoGenerate = true) val uid: Int = 0 )
在上面的代码中,一张first_user的表就建好了。里面有几个注解需要说明一些
- @ColumnInfo(用来指定数据库表中对应的列明,属性 name 可以单独指定列名,如果不设置,默认为变量名作为列名)
- @PrimaryKey (用来指定主键的,该注解修饰的字段属性必须为 int ,long等整型,属性autoGenerate 可以设置主键自增)
在实际使用过中,以上写法其实是有问题的,上面代码指定了 uid做为主键,并且设置了自增,但是该属性放在构造方法的位置,书实例化User 这个对象的时候,这个 uid 的值是必须填写的。所以要不指定主键,让其自增,该如下实现
@Entity(tableName = "first_user") data class User( @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") var lastName: String, @ColumnInfo(name = "age") var age: Int = 0 ) { @PrimaryKey(autoGenerate = true) var uid: Int = 0 }
-
Dao(包含用于访问数据库的方法)
@Dao interface UserDao { }
这个一个接口,包括了管理数据的方法,以 曾删改查为例
@Dao interface UserDao { @Query("SELECT * FROM user") fun getUser(): MutableList<User>//查询所有 @Delete fun delete(user: User)//删除指定的User @Update(entity = User::class) fun updateUser(user: User)//修改指定的User @Insert fun insertUser(vararg users: User)//插入数据,可以是单个,也可以是多个 }
-
AppDatabase Dao管理对象
@Database(entities = [User::class],version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun carDao():CarDao ... }
在以上代码中有几个注解简单说下
- @Database Marks a class as a RoomDatabase. 标记一个类作为 RoomDatabase,被这个注解修饰类是个抽象类,并且需要继承 RoomDatabase,上面代码就是该类的固定写法
- entities属性,该属性 修饰的是一个数组,该数组需要加入 被 @Entity 注解修饰的类,也就是数据库中的表
- version 属性,该属性制动了数据库的版本
-
创建数据库实例
创建上述文件后,使用以下代码获取已创建的数据库的实例
val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "database-name" ).build()
上述代码已经很清晰的展示了数据库对象的创建过程,“database-name”,该字符串指定了数据库的名称,可以按需求改成自己设置的名称
注意:在单个进程中运行,在实例化
AppDatabase
对象时应遵循单例设计模式。每个RoomDatabase实例的成本相当高,如下
object DBHelper {
val db = Room.databaseBuilder(
App.context,
AppDatabase::class.java,
"snukaisens"
).build()
}
-
测试
class MainViewModel : ViewModel() { fun getUser(): MutableList<User> { return DBHelper.db.getUserDao().getUser() } fun insert(vararg users: User) { DBHelper.db.getUserDao().insertUser(*users) } fun findByName(firstName: String,lastName:String):User { return DBHelper.db.getUserDao().findByName(firstName, lastName) } fun updateUser(user: User) { DBHelper.db.getUserDao().updateUser(user) } } ... //需要说明的是,关于所有数据库相关的操作,都需要在子线程中执行 GlobalScope.launch { val user = viewModel.getUser()//查询 println("第一次取值 = $user") val insertUser = User("Tom","Json") val insertUser1 = User("Tom1","Json1") val insertUser2 = User("Tom2","Json2") val insertUser3 = User("Tom3","Json3") val insertUser4 = User("Tom4","Json4") viewModel.insert(insertUser,insertUser1,insertUser2,insertUser3,insertUser4)//批量插入 val user1 = viewModel.getUser()//再次查询 println(message = "第二次取值 = $user1") val onlyUser = viewModel.findByName("Tom4", "Json4")//按条件查询 onlyUser.lastName = "sunjian" viewModel.updateUser(onlyUser)//修改 }
执行结果如下
2021-03-12 15:16:18.211 30145-30269/com.example.firstapp I/System.out: 第一次取值 = [] 2021-03-12 15:16:18.213 30145-30269/com.example.firstapp I/System.out: 第二次取值 = [User(firstName=Tom, lastName=Json, age=0), User(firstName=Tom1, lastName=Json1, age=0), User(firstName=Tom2, lastName=Json2, age=0), User(firstName=Tom3, lastName=Json3, age=0), User(firstName=Tom4, lastName=Json4, age=0)] 2021-03-12 15:16:18.215 30145-30269/com.example.firstapp I/System.out: 第三次取值 = [User(firstName=Tom, lastName=Json, age=0), User(firstName=Tom1, lastName=Json1, age=0), User(firstName=Tom2, lastName=Json2, age=0), User(firstName=Tom3, lastName=Json3, age=0), User(firstName=Tom4, lastName=sunjian, age=0)]
数据库升级
在开发过程中难免会碰到数据库表结构的改变,碰到这种情况,我们就需要对数据库表的结构进行升级,sqlite支持的数据库表结构的操作说明:
SQLite supports a limited subset of ALTER TABLE. The ALTER TABLE command in SQLite allows the user to rename a table or to add a new column to an existing table. It is not possible to rename a column, remove a column, or add or remove constraints from a table.
大致意思就是说,支持字段的添加和修改,不支持删除。所以针对数据库结构的升级就只是正对字段的添加和修改
- 数据库表字段添加
我们正对之前Entity类进行操作,新增加一个e_mail字段
@Entity()
data class User(
@ColumnInfo(name = "first_name")
val firstName: String,
@ColumnInfo(name = "last_name")
var lastName: String,
@ColumnInfo(name = "age")
var age: Int = 0
) {
@PrimaryKey(autoGenerate = true)
var uid: Int = 0
@ColumnInfo
var email: String = ""//新增的数据库字段
}
- 升级数据库的版本
@Database(entities = [User::class],version = 2)//版本号有原来的 1 -> 2
abstract class AppDatabase : RoomDatabase() {
abstract fun getUserDao(): UserDao
}
- 数据库对象中添加迁移配置
val db = Room.databaseBuilder(
App.context,
AppDatabase::class.java,
"snukaisens"
).addMigrations(Migration_1_2())
.build()
//此处需要重点说明一下,添加字段之后,需要设置 NOT NULL属性,而且需要给默认值,要不然数据库迁移过程中就会提示,创建的数据库信息,和预期的不一致,从而导致闪退
class Migration_1_2 : Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table User add column email TEXT NOT NULL DEFAULT ''")
}
}
数据库 User表中添加的
e_mail
,需要修改的内容如下:
//1.第一处需要修改的地方 修改字段
@Entity()
data class User(
@ColumnInfo(name = "first_name")
val firstName: String,
@ColumnInfo(name = "last_name")
var lastName: String,
@ColumnInfo(name = "age")
var age: Int = 0
) {
@PrimaryKey(autoGenerate = true)
var uid: Int = 0
@ColumnInfo
var e_mail: String = ""//字段重新修改
}
//2.修改版本号,记住这个版本
@Database(entities = [User::class],version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun getUserDao(): UserDao
}
//3.添加迁移配置
object DBHelper {
val db = Room.databaseBuilder(
App.context,
AppDatabase::class.java,
"snukaisens"
).addMigrations(Migration_1_2(),Migration_2_3())//注意此处需要传的参数是可变参数,直接添加就行了,不需要把之前的都删了
.build()
class Migration_1_2 : Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table User add column email TEXT NOT NULL DEFAULT ''")
}
}
//修改字段 Migration 这个类构造中传的字段就是 数据库的升级前和升级后的版本号,一定要和 2 中的数字对应
class Migration_2_3 : Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table User rename column email to e_mail")
}
}
}
到此,数据库的修改就完成了。
数据库加密
在实际开发过程过程中,设计到安全问题,手机root过之后,用户可以随意拿到数据库文件,进行查看。针对一些敏感的数据库数据,需要对其进行加密。本文采用
库进行操作,操作很简单。
//引入依赖
implementation "net.zetetic:android-database-sqlcipher:4.4.2"
...
//添加配置
private val factory = SupportFactory("xxxxxx".toByteArray())//此处xxxxxx 用户根据自己的情况自己配置
val db = Room.databaseBuilder(
App.context,
AppDatabase::class.java,
"snukaisens"
).addMigrations(Migration_1_2(), Migration_2_3())
.openHelperFactory(factory)//添加factory
.build()
这样就可以了,数据库加密就完成了,是不是很简单