欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android數(shù)據(jù)庫(kù)Room的實(shí)際使用過程總結(jié)

 更新時(shí)間:2025年01月07日 10:38:45   作者:記憶里模糊的小時(shí)候  
這篇文章主要給大家介紹了關(guān)于Android數(shù)據(jù)庫(kù)Room的實(shí)際使用過程,詳細(xì)介紹了如何創(chuàng)建實(shí)體類、數(shù)據(jù)訪問對(duì)象(DAO)和數(shù)據(jù)庫(kù)抽象類,需要的朋友可以參考下

前言

最近我負(fù)責(zé)開發(fā)一個(gè)基于Android系統(tǒng)的平板應(yīng)用程序,在項(xiàng)目中涉及到數(shù)據(jù)庫(kù)操作的部分,我們最終決定采用Room數(shù)據(jù)庫(kù)框架來(lái)實(shí)現(xiàn)。在實(shí)際使用過程中,我遇到了一些挑戰(zhàn)和問題,現(xiàn)在我想將這些經(jīng)驗(yàn)記錄下來(lái),以便未來(lái)參考和改進(jìn)。

一、Room的基本使用

1.項(xiàng)目配置

在開發(fā)這個(gè)Android項(xiàng)目時(shí),我決定將數(shù)據(jù)庫(kù)操作代碼獨(dú)立成一個(gè)模塊,這樣做有助于保持代碼的整潔和模塊化。在這個(gè)模塊中,我選擇了Kotlin作為編程語(yǔ)言,并使用了Kotlin 1.5.21版本。為了支持Kotlin開發(fā)和編譯,我需要在項(xiàng)目中包含兩個(gè)插件:kotlin-android 和 kotlin-kapt。這兩個(gè)插件分別負(fù)責(zé)Kotlin代碼的Android特定功能支持和注解處理,確保代碼能夠正確編譯和運(yùn)行。

plugins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'kotlin-kapt'
}

采用了Room框架,具體版本為2.3.0。由于Room框架在不同版本之間可能存在API差異,因此在這里特別指出我所使用的版本,以便于在遇到問題時(shí)能夠準(zhǔn)確地查找和解決問題,同時(shí)也使用到了協(xié)程,所有依賴如下:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    // Room數(shù)據(jù)庫(kù)版本
    def room_version = "2.3.0"
    implementation "androidx.room:room-runtime:$room_version"
    // Kapt
    kapt "androidx.room:room-compiler:$room_version"
    // room-ktx
    implementation "androidx.room:room-ktx:$room_version"
    // 協(xié)程
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
}

2.創(chuàng)建實(shí)體類(Entity)

在Room數(shù)據(jù)庫(kù)框架中,實(shí)體類是用來(lái)映射數(shù)據(jù)庫(kù)表的。每個(gè)實(shí)體類代表一個(gè)數(shù)據(jù)庫(kù)表,而實(shí)體類的屬性則對(duì)應(yīng)表中的列。以下是一個(gè)使用Kotlin語(yǔ)言編寫的Airport實(shí)體類的示例代碼,其中id字段被標(biāo)記為主鍵:

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "airports") // 指定表名
data class Airport(
    @PrimaryKey(autoGenerate = true) val id: Int, // 主鍵,自動(dòng)生成
    val name: String, // 機(jī)場(chǎng)名稱
    val city: String, // 所在城市
    val country: String // 所在國(guó)家
)

3.創(chuàng)建數(shù)據(jù)訪問對(duì)象(DAO - Data Access Object)

DAO 用于定義訪問數(shù)據(jù)庫(kù)的方法,比如插入、查詢、更新、刪除等操作。以下是針對(duì) Airport實(shí)體類創(chuàng)建的 AirportDao示例:

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import androidx.room.Delete

@Dao
interface AirportDao {
    // 插入單個(gè)Airport對(duì)象
    @Insert
    suspend fun insert(airport: Airport)

    // 插入多個(gè)Airport對(duì)象
    @Insert
    suspend fun insertAll(vararg airports: Airport)

    // 根據(jù)ID查詢Airport對(duì)象
    @Query("SELECT * FROM airports WHERE id = :id")
    suspend fun getAirportById(id: Int): Airport?

    // 更新Airport對(duì)象
    @Update
    suspend fun update(airport: Airport)

    // 刪除單個(gè)Airport對(duì)象
    @Delete
    suspend fun delete(airport: Airport)

    // 刪除所有Airport對(duì)象
    @Query("DELETE FROM airports")
    suspend fun deleteAll()
}

在這個(gè)AirportDao接口中:

  • @Dao 注解標(biāo)記這個(gè)接口為一個(gè)DAO接口。
  • @Insert注解用于定義插入操作的方法。insert()方法可以插入單個(gè)Airport對(duì)象,而insertAll()方法可以插入多個(gè)Airport對(duì)象。
  • @Query 注解用于定義自定義SQL查詢的方法。getAirportById()方法通過ID查詢單個(gè)Airport對(duì)象。
  • @Update 注解用于定義更新操作的方法。update()方法更新一個(gè)Airport對(duì)象。
  • @Delete注解用于定義刪除操作的方法。delete()方法可以刪除單個(gè)Airport對(duì)象,而deleteAll()方法可以刪除所有Airport對(duì)象。
  • suspend關(guān)鍵字用于標(biāo)記一個(gè)函數(shù)為掛起函數(shù)(suspend function),這是Kotlin協(xié)程(coroutines)的一個(gè)重要特性。掛起函數(shù)可以暫停和恢復(fù)其執(zhí)行,而不會(huì)阻塞線程

這些方法定義了與Airport實(shí)體類對(duì)應(yīng)的數(shù)據(jù)庫(kù)表進(jìn)行交互的基本操作。Room框架會(huì)在編譯時(shí)自動(dòng)實(shí)現(xiàn)這些接口方法,開發(fā)者無(wú)需手動(dòng)編寫實(shí)現(xiàn)代碼。

4. 創(chuàng)建數(shù)據(jù)庫(kù)抽象類(Database)

在Room數(shù)據(jù)庫(kù)框架中,你需要?jiǎng)?chuàng)建一個(gè)繼承自RoomDatabase的抽象類,這個(gè)類將作為數(shù)據(jù)庫(kù)的訪問入口,并定義與實(shí)體類和DAO的關(guān)聯(lián)。以下是一個(gè)示例代碼,展示了如何創(chuàng)建這樣的數(shù)據(jù)庫(kù)類,并與Airport實(shí)體類和AirportDao接口關(guān)聯(lián):

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters

// 定義數(shù)據(jù)庫(kù)的版本
@Database(entities = [Airport::class], version = 1, exportSchema = false)
@TypeConverters(YourTypeConverters::class) // 如果有自定義類型轉(zhuǎn)換器,在這里指定
abstract class AppDatabase : RoomDatabase() {

    // 提供獲取DAO實(shí)例的方法
    abstract fun airportDao(): AirportDao

    // Companion object to create an instance of AppDatabase
    companion object {
        // Singleton instance of the database
        @Volatile
        private var instance: AppDatabase? = null

        // Method to get the database instance
        fun getInstance(context: Context): AppDatabase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { inst ->
                    instance = inst
                }
            }
        }

        // Method to build the database
        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(
                context.applicationContext,
                AppDatabase::class.java,
                "app_database"
            ).build()
        }
    }
}

在這個(gè)AppDatabase類中:

  • @Database注解定義了數(shù)據(jù)庫(kù)包含的實(shí)體類(entities)和數(shù)據(jù)庫(kù)版本(version)。exportSchema屬性用于控制是否導(dǎo)出數(shù)據(jù)庫(kù)的schema文件,通常在開發(fā)階段設(shè)置為true,而在生產(chǎn)環(huán)境中設(shè)置為false。
  • @TypeConverters注解用于指定一個(gè)或多個(gè)類,這些類包含自定義的類型轉(zhuǎn)換器,如果需要將非標(biāo)準(zhǔn)類型(如Date或URL)存儲(chǔ)在數(shù)據(jù)庫(kù)中,這些轉(zhuǎn)換器是必需的。
  • abstract fun airportDao(): AirportDao提供了一個(gè)抽象方法,用于獲取AirportDao的實(shí)例,這樣我們就可以在數(shù)據(jù)庫(kù)類中執(zhí)行對(duì)Airport表的操作。
  • companion
    object提供了一個(gè)單例模式的實(shí)現(xiàn),用于獲取AppDatabase的實(shí)例。getInstance()方法確保整個(gè)應(yīng)用中只有一個(gè)數(shù)據(jù)庫(kù)實(shí)例被創(chuàng)建。buildDatabase()方法用于實(shí)際構(gòu)建和配置數(shù)據(jù)庫(kù)。

**請(qǐng)注意!**你需要將YourTypeConverters::class替換為實(shí)際包含自定義類型轉(zhuǎn)換器的類的名稱,如果你沒有自定義類型轉(zhuǎn)換器,可以省略@TypeConverters注解。此外,context參數(shù)需要從你的應(yīng)用上下文傳遞給getInstance()方法,以確保數(shù)據(jù)庫(kù)正確地與應(yīng)用的生命周期關(guān)聯(lián)。

5. 使用數(shù)據(jù)庫(kù)

在Android的Activity中使用數(shù)據(jù)庫(kù)進(jìn)行操作時(shí),可以在協(xié)程中執(zhí)行這些操作,以避免阻塞主線程。以下是在Activity中使用協(xié)程與Room數(shù)據(jù)庫(kù)進(jìn)行交互的簡(jiǎn)單示例代碼片段:

import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.*

class AirportActivity : AppCompatActivity() {

    private val viewModel by viewModels<AirportViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_airport)

        // 啟動(dòng)協(xié)程來(lái)插入數(shù)據(jù)
        lifecycleScope.launch {
            viewModel.insertAirport(Airport(0, "Moonshot International", "Shanghai", "China"))
        }

        // 啟動(dòng)協(xié)程來(lái)查詢數(shù)據(jù)
        lifecycleScope.launch {
            val airport = viewModel.getAirportById(1) // 假設(shè)ID為1
            airport.observe(this@AirportActivity, { airport ->
                Log.d("AirportActivity", "Airport Name: ${airport?.name}")
            })
        }
    }
}

在這個(gè)Activity中:

  • lifecycleScope是一個(gè)與Activity生命周期綁定的協(xié)程作用域,它確保協(xié)程在Activity銷毀時(shí)取消。
  • launch是一個(gè)協(xié)程構(gòu)建器,用于啟動(dòng)一個(gè)新的協(xié)程。
  • insertAirport方法在協(xié)程中被調(diào)用,用于插入新的機(jī)場(chǎng)信息。
    getAirportById方法返回一個(gè)LiveData<Airport?>對(duì)象,它在協(xié)程中被觀察,以便在機(jī)場(chǎng)信息變化時(shí)更新UI。

請(qǐng)注意,AirportViewModel需要正確實(shí)現(xiàn),并且包含insertAirport和getAirportById方法。這些方法應(yīng)該在ViewModel中使用viewModelScope而不是lifecycleScope,因?yàn)関iewModelScope是與ViewModel的生命周期綁定的,而不是Activity。

以下是AirportViewModel的示例實(shí)現(xiàn):

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*

class AirportViewModel : ViewModel() {

    private val database = AppDatabase.getInstance(applicationContext) // 假設(shè)這是全局可訪問的context
    private val airportDao = database.airportDao()

    fun insertAirport(airport: Airport) {
        viewModelScope.launch {
            airportDao.insert(airport)
        }
    }

    fun getAirportById(id: Int): LiveData<Airport?> {
        return liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
            emit(airportDao.getAirportById(id))
        }
    }
}

至此Room簡(jiǎn)單的使用已經(jīng)說(shuō)完了,這些步驟構(gòu)成了Room數(shù)據(jù)庫(kù)在Android應(yīng)用中的簡(jiǎn)單使用流程。Room提供了一個(gè)抽象層,幫助開發(fā)者以更聲明式和類型安全的方式進(jìn)行數(shù)據(jù)庫(kù)操作,同時(shí)利用協(xié)程簡(jiǎn)化了異步編程。

二、Room使用過程遇到的問題

1.聲明表中字段可以為null

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "airports") // 指定表名
data class Airport(
    @PrimaryKey(autoGenerate = true) val id: Int, // 主鍵,自動(dòng)生成
    val name: String, // 機(jī)場(chǎng)名稱
    val city: String, // 所在城市
    val country: String // 所在國(guó)家
)

如果在使用Room數(shù)據(jù)庫(kù)時(shí),需要在實(shí)體類中允許某些字段存儲(chǔ)空值,可以直接將這些字段聲明為可空類型。這樣,即使在插入數(shù)據(jù)時(shí)這些字段的值為空,數(shù)據(jù)庫(kù)操作也能正常進(jìn)行。具體來(lái)說(shuō),只需在實(shí)體類中將相應(yīng)的變量聲明為String?、Int?等可空類型,Room就會(huì)允許這些字段在數(shù)據(jù)庫(kù)中存儲(chǔ)空值,代碼如下:

import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "airports") // 指定表名
class Airport {
    @PrimaryKey(autoGenerate = true)
    var id: Int = 0 // 主鍵,自動(dòng)生成
    var name: String? = null // 機(jī)場(chǎng)名稱
    var city: String? = null// 所在城市
    var country: String? = null // 所在國(guó)家
}

2.數(shù)據(jù)庫(kù)升級(jí)

當(dāng)你在Room數(shù)據(jù)庫(kù)的實(shí)體類中添加了一個(gè)新的字段后,如果在運(yùn)行應(yīng)用時(shí)遇到了崩潰,并且出現(xiàn)了異常信息,這通常是因?yàn)镽oom數(shù)據(jù)庫(kù)的遷移問題。Room需要知道如何處理數(shù)據(jù)庫(kù)結(jié)構(gòu)的變化,比如添加、刪除或修改字段。如果沒有正確處理這些變化,Room在嘗試訪問數(shù)據(jù)庫(kù)時(shí)就會(huì)拋出異常,異常信息如下:

Room cannot verify the data integrity, Looks like vou’ve changed schema but forgot to update the version number, You can simply . fix this by increasing the version number.

遇到數(shù)據(jù)庫(kù)結(jié)構(gòu)變更時(shí),通常有兩種處理方法:

第一種卸載并重新安裝應(yīng)用:這是一種簡(jiǎn)單直接的方法,通過卸載應(yīng)用再重新安裝,應(yīng)用將創(chuàng)建全新的數(shù)據(jù)庫(kù),從而自動(dòng)包含所有新的表結(jié)構(gòu)和字段變更。另一種方法是進(jìn)行數(shù)據(jù)庫(kù)升級(jí),下面是數(shù)據(jù)庫(kù)升級(jí)的步驟:

  • 更新實(shí)體類:在實(shí)體類中添加新的字段。例如,如果你想為Airport實(shí)體類添加一個(gè)新字段,你可以直接聲明這個(gè)字段為可空類型,如val newField: String? = null。
  • 增加數(shù)據(jù)庫(kù)版本號(hào):在@Database注解中增加版本號(hào)。例如,如果你的數(shù)據(jù)庫(kù)當(dāng)前版本是1,那么在添加新字段后,將版本號(hào)增加到2:@Database(entities = [Airport::class], version = 2) 。
  • 創(chuàng)建Migration遷移類:在RoomDatabase中定義Migration對(duì)象,指定如何從舊版本遷移到新版本。例如,為Airport實(shí)體類添加新字段的遷移可以這樣定義:
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
   override fun migrate(database: SupportSQLiteDatabase) {
        // 執(zhí)行SQL
       database.execSQL("ALTER TABLE airports ADD COLUMN newField TEXT")
   }
}
  • 將Migration添加到數(shù)據(jù)庫(kù)構(gòu)建中:在構(gòu)建數(shù)據(jù)庫(kù)時(shí),通過addMigrations方法添加Migration對(duì)象。例如
val database = Room.databaseBuilder(
    context.applicationContext,
    AppDatabase::class.java, 
    "app_database"
).addMigrations(MIGRATION_1_2).build()

這樣,當(dāng)應(yīng)用啟動(dòng)時(shí),Room會(huì)自動(dòng)執(zhí)行Migration中定義的遷移操作。
通過這些步驟,你可以平滑地將Room數(shù)據(jù)庫(kù)升級(jí)到新版本,同時(shí)添加新的字段。如果用戶之前安裝的數(shù)據(jù)庫(kù)版本較低,Room會(huì)按照定義的Migration順序依次執(zhí)行,直到達(dá)到最新的數(shù)據(jù)庫(kù)版本。

3.如何關(guān)聯(lián)外鍵ForeignKey

發(fā)現(xiàn)有些人不知道什么是外鍵:這里簡(jiǎn)單說(shuō)明一下:

外鍵的主要作用如下:

  • 建立關(guān)聯(lián)關(guān)系:用于在不同表之間建立聯(lián)系,清晰體現(xiàn)實(shí)體之間的對(duì)應(yīng)關(guān)系,如機(jī)場(chǎng)與跑道的所屬關(guān)系,方便進(jìn)行多表聯(lián)合查詢等操作。

  • 維護(hù)數(shù)據(jù)完整性:防止在插入或更新數(shù)據(jù)時(shí)出現(xiàn)無(wú)效數(shù)據(jù),確保子表中的外鍵值在父表的主鍵值中存在或?yàn)?NULL,保證數(shù)據(jù)的準(zhǔn)確性和一致性。

  • 實(shí)現(xiàn)級(jí)聯(lián)操作:定義級(jí)聯(lián)規(guī)則,當(dāng)父表中的記錄發(fā)生刪除或更新時(shí),子表中對(duì)應(yīng)的記錄可按規(guī)則自動(dòng)進(jìn)行相應(yīng)操作,確保數(shù)據(jù)在不同表之間的協(xié)調(diào)一致。

  • 約束數(shù)據(jù)變化:通過參照完整性約束,控制數(shù)據(jù)在表之間的更新和刪除傳播方式,保證數(shù)據(jù)的修改和更新符合特定的業(yè)務(wù)邏輯和要求。

在 Room 中聲明外鍵可以通過在實(shí)體類中使用@ForeignKey注解來(lái)實(shí)現(xiàn)。以下是一個(gè)示例,展示了如何在機(jī)場(chǎng)表和機(jī)場(chǎng)跑道表之間聲明外鍵關(guān)聯(lián):

  • 定義機(jī)場(chǎng)表實(shí)體類
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "airport")
data class Airport(
    @PrimaryKey(autoGenerate = true)
    var airportId: Int = 0,
    var airportName: String = ""
)
  • 定義機(jī)場(chǎng)跑道表實(shí)體類(Runway)并聲明外鍵
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity(
    tableName = "runway",
    foreignKeys = [ForeignKey(
        entity = Airport::class,
        parentColumns = ["airportId"],
        childColumns = ["airportIdFK"],
        onDelete = ForeignKey.CASCADE
    )]
)
data class Runway(
    @PrimaryKey(autoGenerate = true)
    var runwayId: Int = 0,
    var airportIdFK: Int = 0,
    var runwayName: String = ""
)

在上述 Runway 實(shí)體類中,使用 @ForeignKey 注解來(lái)聲明外鍵關(guān)系,各參數(shù)含義和 Java 版本中的一致:

  • entity:指定關(guān)聯(lián)的實(shí)體類型,這里關(guān)聯(lián)的是 Airport 類。
  • parentColumns:表示在關(guān)聯(lián)的父實(shí)體(即 Airport)中對(duì)應(yīng)的主鍵列名,此處為 airportId。
  • childColumns:代表在當(dāng)前實(shí)體(Runway)中作為外鍵的列名,也就是 airportIdFK。
  • onDelete:定義了當(dāng)父表(Airport 表)中對(duì)應(yīng)的主鍵記錄被刪除時(shí)的行為,這里設(shè)置為 CASCADE,意味著級(jí)聯(lián)刪除,比如刪除某個(gè)機(jī)場(chǎng)記錄時(shí),與之關(guān)聯(lián)的跑道記錄也會(huì)自動(dòng)刪除。除了 CASCADE 之外,還有以下幾種常見的類型及其含義:

-NO_ACTION含義:當(dāng)父表中的記錄被刪除或更新時(shí),子表中的外鍵列不做任何操作,這可能會(huì)導(dǎo)致子表中的外鍵引用無(wú)效的父鍵值,從而產(chǎn)生孤立的數(shù)據(jù),破壞數(shù)據(jù)的完整性。
示例:在 文章表 和 評(píng)論表 的關(guān)聯(lián)中,如果使用 NO_ACTION,當(dāng)一篇文章被刪除時(shí),評(píng)論表中對(duì)應(yīng)的文章外鍵值不會(huì)改變,仍然保留原來(lái)的文章 ID,即使該文章已經(jīng)不存在了,這就導(dǎo)致了評(píng)論表中的這些評(píng)論與實(shí)際不存在的文章產(chǎn)生了孤立的關(guān)聯(lián)。
SET_NULL含義:當(dāng)父表中的記錄被刪除或更新時(shí),子表中對(duì)應(yīng)的外鍵列的值將被設(shè)置為 NULL。
示例:假設(shè)有 用戶表 和 訂單表,訂單表 中的 用戶ID 是外鍵關(guān)聯(lián)到 用戶表 的主鍵。當(dāng)一個(gè)用戶被刪除時(shí),該用戶的所有訂單記錄中的 用戶ID 字段將被設(shè)置為 NULL,表示這些訂單與任何用戶都不再關(guān)聯(lián),但訂單記錄本身仍然保留在 訂單表 中。
SET_DEFAULT含義:當(dāng)父表中的記錄被刪除或更新時(shí),子表中對(duì)應(yīng)的外鍵列的值將被設(shè)置為其默認(rèn)值。
示例:若 訂單表 中的 用戶ID 外鍵字段有一個(gè)默認(rèn)值為 0,當(dāng)關(guān)聯(lián)的用戶被刪除時(shí),該用戶的所有訂單記錄中的 用戶ID 將被設(shè)置為 0,以此來(lái)表示一種特殊的狀態(tài)或無(wú)關(guān)聯(lián)的情況。
RESTRICT含義:當(dāng)父表中的記錄被刪除或更新時(shí),如果子表中存在對(duì)應(yīng)的關(guān)聯(lián)記錄,則拒絕父表的刪除或更新操作,從而防止出現(xiàn)孤立的子記錄,確保數(shù)據(jù)的一致性和完整性。
示例:在 部門表 和 員工表 的關(guān)系中,員工表 通過外鍵關(guān)聯(lián)到 部門表 的主鍵。如果試圖刪除一個(gè)部門,而該部門下還有員工,那么由于 RESTRICT 約束,數(shù)據(jù)庫(kù)將不允許執(zhí)行這個(gè)刪除操作,避免出現(xiàn)員工所屬部門不存在的不合理情況。

4.使用事務(wù)@Transaction

在 Room 中,事務(wù)是一種重要的機(jī)制,用于確保多個(gè)數(shù)據(jù)庫(kù)操作的原子性,即要么所有操作都成功執(zhí)行,數(shù)據(jù)庫(kù)狀態(tài)被完整更新;要么所有操作都失敗回滾,數(shù)據(jù)庫(kù)保持初始狀態(tài),從而有效地維護(hù)數(shù)據(jù)的一致性。以下是關(guān)于 Room 中事務(wù)的詳細(xì)介紹:

事務(wù)的必要性

  • 在實(shí)際的數(shù)據(jù)庫(kù)操作中,常常會(huì)有多個(gè)相關(guān)的操作需要作為一個(gè)整體來(lái)執(zhí)行。例如,在一個(gè)銀行轉(zhuǎn)賬系統(tǒng)中,從一個(gè)賬戶扣除一定金額并在另一個(gè)賬戶增加相應(yīng)金額,這兩個(gè)操作必須同時(shí)成功或同時(shí)失敗,否則就會(huì)導(dǎo)致數(shù)據(jù)不一致,如賬戶余額出現(xiàn)錯(cuò)誤等問題。事務(wù)機(jī)制正是為了滿足這種需求而設(shè)計(jì)的,它能夠保證在復(fù)雜的操作場(chǎng)景下數(shù)據(jù)的準(zhǔn)確性和完整性。

使用方法

以下是一個(gè)使用事務(wù)進(jìn)行多表查詢的例子,還以Airport和Runway這兩個(gè)實(shí)體類為例,它們之間存在關(guān)聯(lián)關(guān)系。

@Dao
interface AirportRunwayDao {
    @Query("SELECT * FROM airports WHERE id = :airportId")
    fun getAirport(airportId: Int): Airport?

    @Query("SELECT * FROM runways WHERE airportId = :airportId")
    fun getRunways(airportId: Int): List<Runway>

    // 事務(wù)性查詢操作
    @Transaction
    fun getAirportWithRunways(airportId: Int): Pair<Airport?, List<Runway>> {
        // 這里的代碼將在一個(gè)事務(wù)中執(zhí)行
        val airport = getAirport(airportId)
        val runways = getRunways(airportId)
        return Pair(airport, runways)
    }
}

5.數(shù)據(jù)庫(kù)文件的位置

在Room數(shù)據(jù)庫(kù)中,創(chuàng)建AppDatabase對(duì)象時(shí),可以指定數(shù)據(jù)庫(kù)文件的名稱,這個(gè)名稱也是數(shù)據(jù)庫(kù)文件的名字。默認(rèn)情況下,Room數(shù)據(jù)庫(kù)文件存儲(chǔ)在應(yīng)用的內(nèi)部存儲(chǔ)目錄下的特定子目錄中。如果需要更改數(shù)據(jù)庫(kù)文件的存儲(chǔ)位置,可以通過指定具體的文件路徑來(lái)實(shí)現(xiàn)。這樣,數(shù)據(jù)庫(kù)文件就會(huì)被創(chuàng)建在指定的路徑下,而不是默認(rèn)的內(nèi)部存儲(chǔ)位置。代碼如下:

private fun buildDatabase(context: Context): AppDatabase {
            val dbPath = "${context.getExternalFilesDir(null)?.absolutePath}/database/test.db"
            return Room.databaseBuilder(
                context.applicationContext,
                AppDatabase::class.java,
                dbPath 
            ).build()
        }

這樣數(shù)據(jù)庫(kù)文件存在的位置,就會(huì)放到指定目錄下。

6.打開已存在的數(shù)據(jù)庫(kù)

在大多數(shù)應(yīng)用場(chǎng)景中,Room數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)使用方法已經(jīng)足夠。但在本次項(xiàng)目中,我們需要軟件具備打開本地已有數(shù)據(jù)庫(kù)文件或?qū)胪獠繑?shù)據(jù)庫(kù)文件的功能。操作步驟與常規(guī)配置相似:

  • 創(chuàng)建實(shí)體類(Entity):定義一個(gè)實(shí)體類來(lái)映射數(shù)據(jù)庫(kù)中的表結(jié)構(gòu)。
  • 創(chuàng)建數(shù)據(jù)訪問對(duì)象(DAO):定義一個(gè)接口,用于執(zhí)行數(shù)據(jù)庫(kù)的增刪改查等操作。

與常規(guī)使用的主要區(qū)別在于,需要將待打開的數(shù)據(jù)庫(kù)文件放置在指定目錄下。在初始化Room數(shù)據(jù)庫(kù)時(shí),指定數(shù)據(jù)庫(kù)文件的路徑:

  • 如果指定路徑下已存在數(shù)據(jù)庫(kù)文件,Room將直接使用該文件。
  • 如果指定路徑下沒有數(shù)據(jù)庫(kù)文件,Room將創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)文件。
    這樣,我們就能夠?qū)崿F(xiàn)對(duì)本地或外部數(shù)據(jù)庫(kù)文件的訪問和管理。

打開外部數(shù)據(jù)時(shí)遇到的問題

當(dāng)遇到

IllegalStateException: Pre-packaged database has an invalid schema: airport
Expected…
…表結(jié)構(gòu)信息
Found:
…表結(jié)構(gòu)信息

當(dāng)遇到類似 “IllagelStateException: Pre-packaged database has an invalid schena: Excepted… Found:” 這樣的報(bào)錯(cuò)時(shí),其背后的原因通常是預(yù)打包數(shù)據(jù)庫(kù)(也就是你準(zhǔn)備打開的外部數(shù)據(jù)文件對(duì)應(yīng)的數(shù)據(jù)庫(kù))的架構(gòu)與 Room 所期望的架構(gòu)出現(xiàn)了不匹配的情況。
那這里所說(shuō)的數(shù)據(jù)庫(kù)架構(gòu),涵蓋了表結(jié)構(gòu)、列定義以及約束等多個(gè)方面的內(nèi)容。常見的導(dǎo)致架構(gòu)不匹配的因素有以下幾種:

  • 一是字段可空聲明不一致。比如在 Room中通過實(shí)體類定義某個(gè)字段是非空的,但在預(yù)打包數(shù)據(jù)庫(kù)里對(duì)應(yīng)的該字段卻允許為空,或者反之,這種差異就會(huì)造成架構(gòu)不一致。
  • 二是數(shù)據(jù)類型不一致。可能在實(shí)體類中定義某個(gè)字段為 Integer 類型,然而預(yù)打包數(shù)據(jù)庫(kù)里對(duì)應(yīng)列的數(shù)據(jù)類型卻是TEXT,不同的數(shù)據(jù)類型設(shè)置會(huì)讓 Room 在驗(yàn)證數(shù)據(jù)庫(kù)架構(gòu)時(shí)判定為不匹配。
  • 三是外鍵約束不一致。例如在 Room 的實(shí)體類中定義了兩張表之間通過外鍵建立了特定的關(guān)聯(lián)關(guān)系,并且設(shè)置了相應(yīng)的外鍵約束規(guī)則,像刪除操作時(shí)的級(jí)聯(lián)方式等,但在預(yù)打包數(shù)據(jù)庫(kù)里對(duì)應(yīng)的表之間的外鍵約束情況與之不同,這同樣會(huì)引發(fā)架構(gòu)方面的問題。

當(dāng)出現(xiàn)這類報(bào)錯(cuò)后,我們需要仔細(xì)對(duì)比異常日志里呈現(xiàn)的兩個(gè)表結(jié)構(gòu),查找究竟是哪個(gè)地方出現(xiàn)了不一致的情況。一旦發(fā)現(xiàn)了問題所在,接下來(lái)就要采取相應(yīng)的解決措施。要么對(duì) Room 中的 Entity 實(shí)體類進(jìn)行修改,使其表結(jié)構(gòu)、字段定義以及約束等各方面與預(yù)打包數(shù)據(jù)庫(kù)的實(shí)際架構(gòu)相符;要么對(duì)預(yù)打包的數(shù)據(jù)庫(kù)文件本身進(jìn)行調(diào)整,從而讓二者的結(jié)構(gòu)能夠達(dá)成一致。只有在確保這兩個(gè)結(jié)構(gòu)完全一致的前提下,才能夠成功連接數(shù)據(jù)庫(kù),避免出現(xiàn)上述的報(bào)錯(cuò)情況。

總結(jié)

到此這篇關(guān)于Android數(shù)據(jù)庫(kù)Room的實(shí)際使用的文章就介紹到這了,更多相關(guān)Android數(shù)據(jù)庫(kù)Room使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android實(shí)現(xiàn)圖片疊加效果的兩種方法

    Android實(shí)現(xiàn)圖片疊加效果的兩種方法

    這篇文章主要介紹了Android實(shí)現(xiàn)圖片疊加效果的兩種方法,結(jié)合實(shí)例形式分析了Android實(shí)現(xiàn)圖片疊加效果的兩種操作方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2016-08-08
  • Android接入U(xiǎn)SB掃碼模塊的方法

    Android接入U(xiǎn)SB掃碼模塊的方法

    這篇文章主要為大家詳細(xì)介紹了Android接入U(xiǎn)SB掃碼模塊的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • android實(shí)現(xiàn)RecyclerView列表單選功能

    android實(shí)現(xiàn)RecyclerView列表單選功能

    這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)RecyclerView列表單選功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • Android Service 服務(wù)不被殺死的妙招

    Android Service 服務(wù)不被殺死的妙招

    這篇文章主要介紹了Android Service 服務(wù)不被殺死的妙招的相關(guān)資料,需要的朋友可以參考下
    2015-11-11
  • 一些有效的Android啟動(dòng)優(yōu)化策略分享

    一些有效的Android啟動(dòng)優(yōu)化策略分享

    在當(dāng)今激烈競(jìng)爭(zhēng)的移動(dòng)應(yīng)用市場(chǎng),應(yīng)用的啟動(dòng)速度直接影響著用戶的第一印象和滿意度,Android的啟動(dòng)優(yōu)化是開發(fā)者必須關(guān)注的關(guān)鍵領(lǐng)域,本文將詳細(xì)介紹一些強(qiáng)大有效的Android啟動(dòng)優(yōu)化策略,幫助你優(yōu)化應(yīng)用的啟動(dòng)過程,為用戶創(chuàng)造更出色的體驗(yàn),需要的朋友可以參考下
    2023-08-08
  • android實(shí)現(xiàn)一鍵鎖屏和一鍵卸載的方法實(shí)例

    android實(shí)現(xiàn)一鍵鎖屏和一鍵卸載的方法實(shí)例

    這篇文章主要給大家介紹了關(guān)于android如何實(shí)現(xiàn)一鍵鎖屏和一鍵卸載的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-05-05
  • Android View刷新機(jī)制實(shí)例分析

    Android View刷新機(jī)制實(shí)例分析

    這篇文章主要介紹了Android View刷新機(jī)制,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android的View刷新機(jī)制功能、原理與具體使用技巧,需要的朋友可以參考下
    2016-02-02
  • Android 隱藏及切換顯示鍵盤

    Android 隱藏及切換顯示鍵盤

    這篇文章主要介紹了Android 隱藏及切換顯示鍵盤的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • 全面解析Android中對(duì)EditText輸入實(shí)現(xiàn)監(jiān)聽的方法

    全面解析Android中對(duì)EditText輸入實(shí)現(xiàn)監(jiān)聽的方法

    這篇文章主要介紹了Android中對(duì)EditText輸入實(shí)現(xiàn)監(jiān)聽的方法,包括一個(gè)仿iOS的帶清除功能的ClearEditText輸入框控件的詳細(xì)使用介紹,需要的朋友可以參考下
    2016-04-04
  • Android在項(xiàng)目中接入騰訊TBS瀏覽器WebView的教程與注意的地方

    Android在項(xiàng)目中接入騰訊TBS瀏覽器WebView的教程與注意的地方

    今天小編就為大家分享一篇關(guān)于Android在項(xiàng)目中接入騰訊TBS瀏覽器WebView的教程與注意的地方,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-10-10

最新評(píng)論