天天看點

Android 使用者如何将Room根據不同賬戶動态分庫方案

前言

開發中需要根據不同使用者,建立不同名稱資料庫。登入使用者關聯自己名稱命名的資料庫,達到分庫目的。也有基于同一個資料庫進行分表的操作。

這裡僅介紹使用Android Room資料庫,如何分庫和關聯已經存在的資料庫。GreenDao資料庫同樣可以進行動态分庫,原理一樣。

​​官方Room連結​​

Room 持久性庫在 SQLite 上提供了一個抽象層,以便在充分利用 SQLite

的強大功能的同時,能夠流暢地通路資料庫。具體來說,Room 具有以下優勢:

  1. 針對 SQL 查詢的編譯時驗證。
  2. 可最大限度減少重複和容易出錯的樣闆代碼的友善注解。
  3. 簡化了資料庫遷移路徑
  4. 支援協程挂起以及flow。可以通過觀察flow來同步更新ui。

​​MVI開發範式​​:采用單向資料流來串聯各層次結構的一種開發範式,可以将UI界面層與業務邏輯完全隔離。差別與mvvm

假定您已經了解了room資料庫如何建立。​​room資料庫建立demo​​

建立Room資料庫

object DbManager {
    private const val DEFAULT_DB_PREFIX = "預設資料庫字首"
    lateinit var roomDb: AppDatabase
    fun getInstance(
        context: Context, userCode: String? = "目前登入使用者的code"
    ): AppDatabase {
        val userCode = userCode ?: DEFAULT_DB_PREFIX
        if (userCode != DEFAULT_DB_PREFIX && roomDb.openHelper.databaseName?.contains(userCode) == true) {
            return roomDb
        } else {
            roomDb = Room.databaseBuilder(
                context,
                AppDatabase::class.java,
                userCode + DATABASE_NAME
            ).setAutoCloseTimeout(30, TimeUnit.SECONDS).build()
        }
        return roomDb
    }
}      

将如上中文替換為具體的内容就可以在新使用者登入成功之後,來觸發資料庫的建立,一般來說資料庫的操作需要滞後到使用者登入成功。當然,也并不是必須将資料庫操作滞後到使用者登入成功。滞後的目的是避免建立一個預設的資料庫,與任何使用者沒有綁定,僅僅在使用者登入成功之前存在。因為登入之後就會關聯一個以使用者code為字首的資料庫,或者建立一個以使用者code為字首的資料。類似 100001_db.

Room資料庫提供對表進行操作的是一個內建RoomDatabase的抽象類中的抽象方法。具體實作由Room api來實作。使用者不需要關注具體如何實作資料庫CRUD操作。

@Database(
    entities = [LoginUser::class,], version =
    1,
    exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {

    abstract fun loginUserDao(): LoginUserDao

}      

如上資料庫動态建立和資料表的操作都已經簡單介紹。但是僅僅動态建立資料庫并不能保證資料庫建立之後,新的操作表會操作新建立的資料庫?

why?why?why?

Android 使用者如何将Room根據不同賬戶動态分庫方案

資料庫建立好了。目前使用者就會和新建立的資料庫進行綁定,如果是已經存在的資料庫就會打開資料庫。為什麼操作的不是新建立的資料庫而是舊資料庫(上一個使用者建立的)

細心的話就能發現,在使用者切換,或者使用者被另一台裝置踢掉賬戶之後更換賬戶登入,已經加載過的xxxDao并沒有更新而是關聯着上一個登陸使用者的資料庫。(如果項目中使用依賴注入架構Hilt或者其他類似的依賴注入架構,那麼就需要重新設計依賴關系。對于@Singleton注釋是否需要進行調整。)

解決了切換賬戶後更新已經加載的xxxDao檔案就可以實作使用者切換後自動關聯以使用者code為字首建立的room資料庫了。

方案一:對于xxxDao的擷取,不進行緩存,每次需要操作表就重新根據資料庫擷取xxxDao.這樣可以保證資料庫對象變更後,之後擷取到的xxxDao都是新資料庫對象對應的Dao,不會擷取到舊資料庫的Dao,因為每次都是使用資料庫Db對象,擷取dao。

方案二: 可以在使用者切換,建立資料庫後。發送一個通知到所有需要操作表的已經加載到記憶體中的類,進行Dao更新。 可以使用flow進行更新。目的就是保證切換使用者後已經加載到記憶體中的操作表的Dao及時更新。

如下:

@Singleton
internal class LocalDataSource @Inject constructor(
    private val appdataBase: AppDatabase
) : ILocalDataSource {

  
    override fun loginUserDao(): LoginUserDao {
        return DbManager.roomDb?.loginUserDao()!!
    }

}


interface ILocalDataSource {
    fun loginUserDao(): LoginUserDao
}      

使用地方隻需要依賴ILocalDataSource 接口。每次操作庫表擷取下對應Dao即可。

@Singleton
class UserRepository @Inject constructor(
    @ApplicationContext context: Context,
    private val localDataSource: ILocalDataSource,
  
) :  {

    fun Dao() = localDataSource.loginUserDao()
    fun getLoginUserList() = Dao().getLoginUsers()
}      

總結: