Room 數(shù)據(jù)庫框架最全攻略
Room 是Google官方推出的Android Sqlite數(shù)據(jù)庫處理框架儿礼,是子啊Sqlite上提供了一個(gè)抽象層醇份,以便在充分利用 SQLite 的強(qiáng)大功能的同時(shí)空郊,能夠流暢地訪問數(shù)據(jù)庫谷饿。本文的目的旨在對(duì)于Room框架可以快速上手未荒,內(nèi)容分為兩部分,第一部分為數(shù)據(jù)庫的基本操作(增接箫、刪攒读、改、查)辛友,第二部數(shù)據(jù)庫的升級(jí)薄扁,加密
基本使用
基本工作的準(zhǔn)備
- 引入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"
...
}
以上準(zhǔn)備工作就做的差不多了,接下來開始建立數(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表就建好了,如果指定表名九默,默認(rè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) val uid: Int = 0 )
在上面的代碼中,一張first_user的表就建好了驼修。里面有幾個(gè)注解需要說明一些
- @ColumnInfo(用來指定數(shù)據(jù)庫表中對(duì)應(yīng)的列明殿遂,屬性 name 可以單獨(dú)指定列名衷恭,如果不設(shè)置隙赁,默認(rèn)為變量名作為列名)
- @PrimaryKey (用來指定主鍵的,該注解修飾的字段屬性必須為 int 榛臼,long等整型耳峦,屬性autoGenerate 可以設(shè)置主鍵自增)
在實(shí)際使用過中恩静,以上寫法其實(shí)是有問題的,上面代碼指定了 uid做為主鍵蹲坷,并且設(shè)置了自增驶乾,但是該屬性放在構(gòu)造方法的位置,書實(shí)例化User 這個(gè)對(duì)象的時(shí)候循签,這個(gè) uid 的值是必須填寫的级乐。所以要不指定主鍵,讓其自增县匠,該如下實(shí)現(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 { }
這個(gè)一個(gè)接口风科,包括了管理數(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ù)乞旦,可以是單個(gè)贼穆,也可以是多個(gè) }
-
AppDatabase Dao管理對(duì)象
@Database(entities = [User::class],version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun carDao():CarDao ... }
在以上代碼中有幾個(gè)注解簡(jiǎn)單說下
- @Database Marks a class as a RoomDatabase. 標(biāo)記一個(gè)類作為 RoomDatabase,被這個(gè)注解修飾類是個(gè)抽象類兰粉,并且需要繼承 RoomDatabase故痊,上面代碼就是該類的固定寫法
- entities屬性,該屬性 修飾的是一個(gè)數(shù)組玖姑,該數(shù)組需要加入 被 @Entity 注解修飾的類愕秫,也就是數(shù)據(jù)庫中的表
- version 屬性浊仆,該屬性制動(dòng)了數(shù)據(jù)庫的版本
-
創(chuàng)建數(shù)據(jù)庫實(shí)例
創(chuàng)建上述文件后,使用以下代碼獲取已創(chuàng)建的數(shù)據(jù)庫的實(shí)例
val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "database-name" ).build()
上述代碼已經(jīng)很清晰的展示了數(shù)據(jù)庫對(duì)象的創(chuàng)建過程豫领,“database-name”,該字符串指定了數(shù)據(jù)庫的名稱舔琅,可以按需求改成自己設(shè)置的名稱
注意:在單個(gè)進(jìn)程中運(yùn)行等恐,在實(shí)例化
AppDatabase
對(duì)象時(shí)應(yīng)遵循單例設(shè)計(jì)模式。每個(gè)RoomDatabase實(shí)例的成本相當(dāng)高备蚓,如下
object DBHelper {
val db = Room.databaseBuilder(
App.context,
AppDatabase::class.java,
"snukaisens"
).build()
}
-
測(cè)試
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) } } ... //需要說明的是,關(guān)于所有數(shù)據(jù)庫相關(guān)的操作,都需要在子線程中執(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ù)庫升級(jí)
在開發(fā)過程中難免會(huì)碰到數(shù)據(jù)庫表結(jié)構(gòu)的改變课蔬,碰到這種情況,我們就需要對(duì)數(shù)據(jù)庫表的結(jié)構(gòu)進(jìn)行升級(jí)郊尝,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.
大致意思就是說二跋,支持字段的添加和修改,不支持刪除流昏。所以針對(duì)數(shù)據(jù)庫結(jié)構(gòu)的升級(jí)就只是正對(duì)字段的添加和修改
- 數(shù)據(jù)庫表字段添加
我們正對(duì)之前Entity類進(jìn)行操作扎即,新增加一個(gè)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ù)庫字段
}
- 升級(jí)數(shù)據(jù)庫的版本
@Database(entities = [User::class],version = 2)//版本號(hào)有原來的 1 -> 2
abstract class AppDatabase : RoomDatabase() {
abstract fun getUserDao(): UserDao
}
- 數(shù)據(jù)庫對(duì)象中添加遷移配置
val db = Room.databaseBuilder(
App.context,
AppDatabase::class.java,
"snukaisens"
).addMigrations(Migration_1_2())
.build()
//此處需要重點(diǎn)說明一下,添加字段之后况凉,需要設(shè)置 NOT NULL屬性谚鄙,而且需要給默認(rèn)值,要不然數(shù)據(jù)庫遷移過程中就會(huì)提示刁绒,創(chuàng)建的數(shù)據(jù)庫信息闷营,和預(yù)期的不一致,從而導(dǎo)致閃退
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.修改版本號(hào)筝闹,記住這個(gè)版本
@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è)類構(gòu)造中傳的字段就是 數(shù)據(jù)庫的升級(jí)前和升級(jí)后的版本號(hào),一定要和 2 中的數(shù)字對(duì)應(yīng)
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ù)庫加密
在實(shí)際開發(fā)過程過程中,設(shè)計(jì)到安全問題捉片,手機(jī)root過之后平痰,用戶可以隨意拿到數(shù)據(jù)庫文件汞舱,進(jìn)行查看。針對(duì)一些敏感的數(shù)據(jù)庫數(shù)據(jù)宗雇,需要對(duì)其進(jìn)行加密昂芜。本文采用
庫進(jìn)行操作,操作很簡(jiǎn)單赔蒲。
//引入依賴
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ù)庫加密就完成了,是不是很簡(jiǎn)單