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包含三個組件,Entity、DaoDatabase

    // 該注解代表數據庫一張表,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閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容