Room 數(shù)據(jù)庫框架最全攻略
Room 是Google官方推出的Android Sqlite數(shù)據(jù)庫處理框架,是子啊Sqlite上提供了一個抽象層,以便在充分利用 SQLite 的強大功能的同時,能夠流暢地訪問數(shù)據(jù)庫。本文的目的旨在對于Room框架可以快速上手,內(nèi)容分為兩部分,第一部分為數(shù)據(jù)庫的基本操作(增、刪、改、查),第二部數(shù)據(jù)庫的升級,加密

基本使用
基本工作的準備
- 引入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"
...
}
以上準備工作就做的差不多了,接下來開始建立數(shù)據(jù)庫表
-
建表
本文已 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(用來指定數(shù)據(jù)庫表中對應的列明,屬性 name 可以單獨指定列名,如果不設置,默認為變量名作為列名)
- @PrimaryKey (用來指定主鍵的,該注解修飾的字段屬性必須為 int ,long等整型,屬性autoGenerate 可以設置主鍵自增)
在實際使用過中,以上寫法其實是有問題的,上面代碼指定了 uid做為主鍵,并且設置了自增,但是該屬性放在構(gòu)造方法的位置,書實例化User 這個對象的時候,這個 uid 的值是必須填寫的。所以要不指定主鍵,讓其自增,該如下實現(xiàn)
@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(包含用于訪問數(shù)據(jù)庫的方法)
@Dao interface UserDao { }這個一個接口,包括了管理數(shù)據(jù)的方法,以 曾刪改查為例
@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)//插入數(shù)據(jù),可以是單個,也可以是多個 } -
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屬性,該屬性 修飾的是一個數(shù)組,該數(shù)組需要加入 被 @Entity 注解修飾的類,也就是數(shù)據(jù)庫中的表
- version 屬性,該屬性制動了數(shù)據(jù)庫的版本
-
創(chuàng)建數(shù)據(jù)庫實例
創(chuàng)建上述文件后,使用以下代碼獲取已創(chuàng)建的數(shù)據(jù)庫的實例
val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "database-name" ).build()上述代碼已經(jīng)很清晰的展示了數(shù)據(jù)庫對象的創(chuàng)建過程,“database-name”,該字符串指定了數(shù)據(jù)庫的名稱,可以按需求改成自己設置的名稱
注意:在單個進程中運行,在實例化
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) } } ... //需要說明的是,關于所有數(shù)據(jù)庫相關的操作,都需要在子線程中執(zhí)行 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)//修改 }執(zhí)行結(jié)果如下
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)]
數(shù)據(jù)庫升級
在開發(fā)過程中難免會碰到數(shù)據(jù)庫表結(jié)構(gòu)的改變,碰到這種情況,我們就需要對數(shù)據(jù)庫表的結(jié)構(gòu)進行升級,sqlite支持的數(shù)據(jù)庫表結(jié)構(gòu)的操作說明:
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.
大致意思就是說,支持字段的添加和修改,不支持刪除。所以針對數(shù)據(jù)庫結(jié)構(gòu)的升級就只是正對字段的添加和修改
- 數(shù)據(jù)庫表字段添加
我們正對之前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 = ""http://新增的數(shù)據(jù)庫字段
}
- 升級數(shù)據(jù)庫的版本
@Database(entities = [User::class],version = 2)//版本號有原來的 1 -> 2
abstract class AppDatabase : RoomDatabase() {
abstract fun getUserDao(): UserDao
}
- 數(shù)據(jù)庫對象中添加遷移配置
val db = Room.databaseBuilder(
App.context,
AppDatabase::class.java,
"snukaisens"
).addMigrations(Migration_1_2())
.build()
//此處需要重點說明一下,添加字段之后,需要設置 NOT NULL屬性,而且需要給默認值,要不然數(shù)據(jù)庫遷移過程中就會提示,創(chuàng)建的數(shù)據(jù)庫信息,和預期的不一致,從而導致閃退
class Migration_1_2 : Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table User add column email TEXT NOT NULL DEFAULT ''")
}
}
數(shù)據(jù)庫 User表中添加的
e_mail,需要修改的內(nèi)容如下:
//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 = ""http://字段重新修改
}
//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())//注意此處需要傳的參數(shù)是可變參數(shù),直接添加就行了,不需要把之前的都刪了
.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 這個類構(gòu)造中傳的字段就是 數(shù)據(jù)庫的升級前和升級后的版本號,一定要和 2 中的數(shù)字對應
class Migration_2_3 : Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table User rename column email to e_mail")
}
}
}
到此,數(shù)據(jù)庫的修改就完成了。
數(shù)據(jù)庫加密
在實際開發(fā)過程過程中,設計到安全問題,手機root過之后,用戶可以隨意拿到數(shù)據(jù)庫文件,進行查看。針對一些敏感的數(shù)據(jù)庫數(shù)據(jù),需要對其進行加密。本文采用
庫進行操作,操作很簡單。
//引入依賴
implementation "net.zetetic:android-database-sqlcipher:4.4.2"
...
//添加配置
private val factory = SupportFactory("xxxxxx".toByteArray())//此處xxxxxx 用戶根據(jù)自己的情況自己配置
val db = Room.databaseBuilder(
App.context,
AppDatabase::class.java,
"snukaisens"
).addMigrations(Migration_1_2(), Migration_2_3())
.openHelperFactory(factory)//添加factory
.build()
這樣就可以了,數(shù)據(jù)庫加密就完成了,是不是很簡單