Android 用户如何将Room根据不同账户动态分库方案
前言
开发中需要根据不同用户,创建不同名称数据库。登录用户关联自己名称命名的数据库,达到分库目的。也有基于同一个数据库进行分表的操作。
这里仅介绍使用Android Room数据库,如何分库和关联已经存在的数据库。GreenDao数据库同样可以进行动态分库,原理一样。
官方Room链接
Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite
的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
- 针对 SQL 查询的编译时验证。
- 可最大限度减少重复和容易出错的样板代码的方便注解。
- 简化了数据库迁移路径
- 支持协程挂起以及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?
数据库创建好了。当前用户就会和新创建的数据库进行绑定,如果是已经存在的数据库就会打开数据库。为什么操作的不是新创建的数据库而是旧数据库(上一个用户创建的)
细心的话就能发现,在用户切换,或者用户被另一台设备踢掉账户之后更换账户登录,已经加载过的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()
}
总结:
将Room根据不同账户动态分库,主要分为两步,一,根据登录用户code+db,创建数据库。二,解决已经加载过的Dao,在用户切换之后自动更新。满足了就可以实现该方案。主要考虑用户切换和用户被踢场景如何更新加载过的Dao。