Jetpack组件之Room使用

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包含三个组件,EntityDaoDatabase

    // 该注解代表数据库一张表,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()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容