Room 是Jetpack中的ORM组件,Room 可以简化SQLite数据库操作。
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
-
添加依赖
// 使用kotlin开发必须使用插件kapt添加room-compiler
plugins {
id 'kotlin-kapt'
}
dependencies {
val roomVersion = "2.4.0"
// Java开发
// 基础功能
implementation("androidx.room:room-runtime:$roomVersion")
annotationProcessor("androidx.room:room-compiler:$roomVersion")
// Kotlin开发(添加上边的kotlin-kapt插件)
// 支持协程(包含基础功能)
implementation("androidx.room:room-ktx:$roomVersion")
// 必须引用
kapt("androidx.room:room-compiler:$roomVersion")
}
Room包含三个组件,Entity、Dao、Database
-
新建Entity
// 该注解代表数据库一张表,tableName为该表名字,不设置则默认类名
// 注解必须有!!tableName可以不设置
@Entity(tableName = "User")
data class User(
// 该标签指定该字段作为表的主键, 自增长。注解必须有!!
@PrimaryKey val id: Int? = null,
// 该注解设置当前属性在数据库表中的列名和类型,注解可以不设置,不设置默认列名和属性名相同
@ColumnInfo(name = "content", typeAffinity = ColumnInfo.TEXT)
val content: String?
// 该标签用来告诉系统忽略该字段或者方法,顾名思义:不生成列
@Ignore
)
-
创建Dao,Dao一定是个接口或抽象类。一个Entity代表着一张表,而每张表都需要一个Dao对象,方便对这张表进行增删改查
@Dao
interface UserDao {
@Query("SELECT * FROM User")
fun getAll(): List<User>
@Query("SELECT * FROM User WHERE content LIKE :content LIMIT 1")
fun findByContent(content: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
-
创建Database,抽象类,继承RoomDatabase
@Database(
// 指定该数据库有哪些表,若需建立多张表,以逗号相隔开
entities = [User::class],
// 指定数据库版本号,后续数据库的升级正是依据版本号来判断的
version = 1
)
abstract class AppDatabase : RoomDatabase() {
// 提供所有Dao,对一一对应的数据库表进行操作
abstract fun getUserDao(): UserDao
companion object {
private const val DB_NAME = "app_name.db"
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext, AppDatabase::class.java,
DB_NAME
).build()
INSTANCE = instance
instance
}
}
}
}
-
代码中调用
class DataRepository{
companion object {
@Volatile
private var instance: DataRepository? = null
@Synchronized
fun getInstance(): DataRepository {
if (instance == null) {
instance = DataRepository()
}
return instance!!
}
}
/**
* 查询用户信息
*/
fun getUser(context: Context): User? {
val userList = AppDatabase.getDatabase(context).getUserDao().getAll()
if (userList.isNullOrEmpty()) {
return null
}
val content = userList[0].content
return Gson().fromJson(content, UserInfoBean::class.java)
}
/**
* 储存用户信息
*
* @param content 用户信息Bean Json
*/
fun saveUser(context: Context, content: String?) {
val dao = AppDatabase.getDatabase(context).getUserDao()
val queryInfo = dao.findFirst()
if (queryInfo == null) {
val user = User(content = content)
dao.insert(user)
} else {
queryInfo.content = content
dao.update(user)
}
}
}
-
使用注意点
1. cannot find implementation for com.aheading.request.database.AppDatabase. AppDatabase_Impl does not exist
译:无法找到com.aheading.request.database.AppDatabase的实现。AppDatabase_Impl不存在。即数据库创建失败
解决方案:
1.1 检查所有注解是否添加
@Entity
@PrimaryKey
@Dao
@Database(
entities = [User::class],
version = 1
)
1.2 检查顶部依赖是否配置正确。若多模块开发,Base模块中已配置相关依赖,其他模块只要用到了Database,也需要在build.gradle中添加如下依赖包,不需要功能依赖:
plugins {
id 'kotlin-kapt'
}
dependencies {
kapt("androidx.room:room-compiler:$roomVersion")
}
2. Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
译:无法在主线程上访问数据库,因为它可能会锁定UI很长一段时间。
解决方案:
方案一:创建数据库时设置允许主线程访问 allowMainThreadQueries(),不推荐!
Room.databaseBuilder(
AppHelper.mContext, AppDatabase::class.java,
DB_NAME
)
// 禁用Room的主线程查询检查(慎用!!!)
.allowMainThreadQueries()
.build()
方案二:子线程中调用,我是使用了协程进行操作。
viewModelScope.launch(Dispatchers.IO) {
val localUser = DataRepository.getInstance().getUser(context)
user.postValue(localUser)
withContext(Dispatchers.Main) {
// 切换到主线程执行UI相关操作
...
}
}
3. Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
译:Room无法验证数据完整性。看起来您已经更改了架构,但忘记更新版本号。你可以通过增加版本号来解决这个问题。就是你修改了数据库,但是没有升级数据库的版本。
解决方案:
第一步:更新数据库的注解配置(entitys和版本号),我这里新增表 PersonTable
@Database(
entities = [User::class,PersonTable::class],
version = 2
)
第二步:添加Migration
数据库升级用到的sql语句,不用自己写,去自动生成的类中copy。否则,自己写和自动生成的语句不一致的话,会报错。默认生成的语句在你的 XxxDatabase_Impl 这个类中,例:AppDatabase_Impl
// AppDatabase_Impl中代码
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `student` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `age` TEXT NOT NULL, `roomId` INTEGER NOT NULL)");
_db.execSQL("CREATE TABLE IF NOT EXISTS `class_room` (`class_id` INTEGER NOT NULL, `class_name` TEXT NOT NULL, PRIMARY KEY(`class_id`))");
_db.execSQL("CREATE TABLE IF NOT EXISTS `address` (`addressId` INTEGER NOT NULL, `addressName` TEXT NOT NULL, PRIMARY KEY(`addressId`))");
_db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
_db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7cbdd6263025181ec070edd36e1118eb')");
}
// 1. 新增数据库版本升级Migration
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
// 添加IF NOT EXISTS和IF EXISTS没坏处
"CREATE TABLE IF NOT EXISTS 'PersonTable'('id' INTEGER, 'content' TEXT, PRIMARY KEY('id'))"
)
}
}
// 2. 数据库新建处addMigrations
Room.databaseBuilder(
AppHelper.mContext, AppDatabase::class.java,
DB_NAME
)
.addMigrations(MIGRATION_1_2)
.build()