RoomDB - 你能连接来自不同数据库的表吗
RoomDB - Can you JOIN tables from different databases
我想知道 RoomDB 是否支持来自 不同 数据库的两个 table 的连接。
假设我有以下实体和数据库:
@Entity
data class Foo(
@PrimaryKey
val fooId: Long,
val barId: Long,
val fooText: String
)
@Dao
interface FooDao {
....
}
@Database(
entities = [Foo::class],
version = 1,
exportSchema = false
)
abstract class FooDatabase : RoomDatabase() {
abstract fun fooDao() : FooDao
}
val fooDatabase = Room
.databaseBuilder(application, FooDatabase::class.java, "Foo.db")
.fallbackToDestructiveMigration()
.build()
@Entity
data class Bar(
@PrimaryKey
val id: Long,
val barText: String
)
@Dao
interface BarDao {
....
}
@Database(
entities = [Bar::class],
version = 1,
exportSchema = false
)
abstract class BarDatabase : RoomDatabase() {
abstract fun barDao() : BarDao
}
val barDatabase = Room
.databaseBuilder(application, BarDatabase::class.java, "Bar.db")
.fallbackToDestructiveMigration()
.build()
data class FooWithBar(
@Embedded
val foo: Foo,
@Relation(
parentColumn = "barId",
entityColumn = "id"
)
val bar: Bar
)
如果 Foo
table 与 Bar
[=] 驻留在不同的数据库中,是否可以编写查询以获取连接模型 FooWithBar
41=]?
我知道如果我在同一数据库中同时拥有 Foo
和 Bar
实体,我可以在 dao 中编写一个查询,如:
@Query("SELECT * FROM foo")
suspend fun getFooWithBar() : FooBar?
并且编译器会根据注释生成一个 SQL 查询,该查询将通过 Foo.barId -> Bar.id
关系.
但我不知道是否可以在不同数据库中跨 table 进行这样的连接。
我知道如果我将这些 table 托管在同一个数据库中,我可以实现这一点,但我想保持我的数据库独立。
我想保持我的数据库独立的事实是否表明有“气味”?
是否依赖领域模型?如果是这样,什么时候将域模型拆分为不同的数据库有哪些好的经验法则?
Does the fact I want to keep my db's seperate indicate a "smell"?
是的,尤其是 Room。它引入了复杂性和低效率。
Is it possible to write a query where I can get the join model FooWithBar if the Foo table resides in a different database than the Bar table?
对于您的简单示例是的,但从示例中可以看出没有实际的 SQL JOIN(没有附加),即您获取 Foo 对象并通过获取适当的 Bar 来模拟 JOIN (或酒吧)。
如果您尝试混合来自不同数据库的实体,那么您将遇到问题,例如
即使添加(编辑)Dao 没有问题,例如:-
@Query("SELECT * FROM foo JOIN bar ON foo.barId = bar.id")
fun getAllFooWithBar(): List<FooWithBar>
根据(来自 Android Studio 的屏幕截图)看起来不错:-
当你编译时你会得到这样的错误:-
E:\AndroidStudioApps\SO67981906KotlinRoomDate\app\build\tmp\kapt3\stubs\debug\a\a\so67981906kotlinroomdate\FooDao.java:22: error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such table: bar)
public abstract void getAllFooWithBar();
^E:\AndroidStudioApps\SO67981906KotlinRoomDate\app\build\tmp\kapt3\stubs\debug\a\a\so67981906kotlinroomdate\FooDao.java:22: error: Not sure how to convert a Cursor to this method's return type (void).
public abstract void getAllFooWithBar();
在单个查询中获取需要来自两个数据库的表的任何内容将超出 Room 的范围,因为每个数据库只知道它自己的表。
但是,如果您将一个数据库附加到另一个数据库,那么您将在一个数据库中拥有两个数据库,但房间不理解它。所以你基本上必须恢复使用 SupportSQLiteDatabase(类似于使用原生 Android SQlite(但有一些限制))。
例子(很简单)
Foo实体
@Entity
data class Foo(
@PrimaryKey
val fooId: Long?,
val barId: Long,
val fooText: String
)
- 基本相同
FooDao
@Dao
interface FooDao {
@Insert
fun insert(foo: Foo): Long
@Query("SELECT * FROM foo")
fun getAllFoos(): List<Foo>
@Query("SELECT * FROM foo WHERE fooId=:fooId")
fun getFooById(fooId: Long): Foo
/* !!!!NO GO!!!!
@Query("SELECT * FROM foo JOIN bar ON foo.barId = bar.id")
fun getAllFooWithBar(): List<FooWithBar>
*/
}
- 一些简单的道
FooDatabase
@Database(
entities = [Foo::class],
version = 1,
exportSchema = false
)
abstract class FooDatabase : RoomDatabase() {
abstract fun fooDao() : FooDao
fun attachBar(context: Context): Boolean {
var rv: Boolean = false
if (instance != null) {
val dbs = this.openHelper?.writableDatabase
val barpath = context.getDatabasePath(BarDatabase.DBNAME)
if (dbs != null) {
dbs.execSQL("ATTACH DATABASE '$barpath' AS $BAR_SCHEMA_NAME")
rv = true
}
}
return rv
}
fun closeInstance() {
if(instance == null) return
if (this.isOpen()) {
this.close()
}
instance = null
}
companion object {
@Volatile
private var instance: FooDatabase? = null
fun getInstanceWithForceOption(context: Context, forceReopen: Boolean = false): FooDatabase {
if (forceReopen) instance?.closeInstance()
if (instance == null) {
instance = Room.databaseBuilder(context,FooDatabase::class.java, DBNAME)
.allowMainThreadQueries()
.addCallback(FOO_CALLBACK)
.build()
}
return instance as FooDatabase
}
fun getInstance(context: Context): FooDatabase {
return getInstanceWithForceOption(context, false)
}
val FOO_CALLBACK = object: RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
}
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
}
}
const val DBNAME: String = "foo.db"
const val BAR_SCHEMA_NAME = "bar_schema"
}
}
- 未使用回调,但如果始终通过附加访问,则可以在 onOpen 中完成附加
栏实体
@Entity
data class Bar(
@PrimaryKey
val id: Long?,
val barText: String
)
- 基本相同
BarDao
@Dao
interface BarDao {
@Insert
fun insert(bar: Bar): Long
@Query("SELECT * FROM bar")
fun getAllBars(): List<Bar>
@Query("SELECT * FROM Bar WHERE id=:id")
fun getBarById(id: Long): Bar
}
条形数据库
@Database(
entities = [Bar::class],
version = 1,
exportSchema = false
)
abstract class BarDatabase : RoomDatabase() {
abstract fun barDao() : BarDao
fun closeInstance() {
if (this.isOpen()) {
this.close()
}
instance = null
}
companion object {
@Volatile
private var instance: BarDatabase? = null
fun getInstanceWithForceOption(context: Context, forceReopen: Boolean = false): BarDatabase {
if (forceReopen) instance?.closeInstance()
if (instance == null) {
instance = Room.databaseBuilder(context,BarDatabase::class.java, DBNAME)
.allowMainThreadQueries()
.addCallback(BAR_CALLBACK)
.build()
}
return instance as BarDatabase
}
fun getInstance(context: Context): BarDatabase {
return getInstanceWithForceOption(context, false)
}
val BAR_CALLBACK = object: RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
}
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
}
}
const val DBNAME: String = "bar.db"
}
}
- 再次回调什么都不做
FooWithBar
class FooWithBar {
var foo: Foo
var bar: Bar
constructor(fooId: Long, fooDao: FooDao, barDao: BarDao) {
this.foo = fooDao.getFooById(fooId)
this.bar = barDao.getBarById(foo.barId)
}
}
- 由于您不能同时获得 Foo 和 Bar,这相当于通过 FooDatabase 获取 Foo 然后通过 BarDatabase 获取关联的 Bar 进行连接。
MainActivity 放在一起 :-
class MainActivity : AppCompatActivity() {
lateinit var foodb: FooDatabase
lateinit var fooDao: FooDao
lateinit var bardb: BarDatabase
lateinit var barDao: BarDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
foodb = FooDatabase.getInstance(this)
fooDao = foodb.fooDao()
bardb = BarDatabase.getInstance(this)
barDao = bardb.barDao()
/* Add some data */
fooDao.insert(Foo(null,barDao.insert(Bar(null,"BAR1")),"FOO1"))
barDao.insert(Bar(null,"BAR UNUSED"))
fooDao.insert(Foo(null,barDao.insert(Bar(null,"BAR2")),"FOO2"))
/* Get equivalent of join (simple) using the FooWithBar */
val allFoosWithBars = mutableListOf<FooWithBar>()
for(foo: Foo in fooDao.getAllFoos()) {
allFoosWithBars.add(FooWithBar(foo.fooId!!,fooDao,barDao))
}
for(fwb: FooWithBar in allFoosWithBars) {
Log.d("FOOBARINFO","Foo is ${fwb.foo.fooText} Bar is ${fwb.bar.barText}")
}
//* Done with the Bar database Room wise
bardb.closeInstance()
foodb.attachBar(this) //<<<<< ATTACHES the Bar database to the Foo
/* Get a Supprort SQLite Database */
var sdb = foodb.openHelper.writableDatabase
/* Query Foo and the attached Bar */
var csr = sdb.query("SELECT * FROM foo JOIN ${FooDatabase.BAR_SCHEMA_NAME}.bar ON foo.barId = ${FooDatabase.BAR_SCHEMA_NAME}.bar.id")
DatabaseUtils.dumpCursor(csr)
csr.close()
}
}
结果
2021-06-16 16:35:04.045 D/FOOBARINFO: Foo is FOO1 Bar is BAR1
2021-06-16 16:35:04.045 D/FOOBARINFO: Foo is FOO2 Bar is BAR2
2021-06-16 16:35:04.092 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@ee9871b
2021-06-16 16:35:04.093 I/System.out: 0 {
2021-06-16 16:35:04.093 I/System.out: fooId=1
2021-06-16 16:35:04.093 I/System.out: barId=1
2021-06-16 16:35:04.093 I/System.out: fooText=FOO1
2021-06-16 16:35:04.093 I/System.out: id=1
2021-06-16 16:35:04.093 I/System.out: barText=BAR1
2021-06-16 16:35:04.093 I/System.out: }
2021-06-16 16:35:04.093 I/System.out: 1 {
2021-06-16 16:35:04.093 I/System.out: fooId=2
2021-06-16 16:35:04.093 I/System.out: barId=3
2021-06-16 16:35:04.093 I/System.out: fooText=FOO2
2021-06-16 16:35:04.093 I/System.out: id=3
2021-06-16 16:35:04.093 I/System.out: barText=BAR2
2021-06-16 16:35:04.094 I/System.out: }
2021-06-16 16:35:04.094 I/System.out: <<<<<
我想知道 RoomDB 是否支持来自 不同 数据库的两个 table 的连接。
假设我有以下实体和数据库:
@Entity
data class Foo(
@PrimaryKey
val fooId: Long,
val barId: Long,
val fooText: String
)
@Dao
interface FooDao {
....
}
@Database(
entities = [Foo::class],
version = 1,
exportSchema = false
)
abstract class FooDatabase : RoomDatabase() {
abstract fun fooDao() : FooDao
}
val fooDatabase = Room
.databaseBuilder(application, FooDatabase::class.java, "Foo.db")
.fallbackToDestructiveMigration()
.build()
@Entity
data class Bar(
@PrimaryKey
val id: Long,
val barText: String
)
@Dao
interface BarDao {
....
}
@Database(
entities = [Bar::class],
version = 1,
exportSchema = false
)
abstract class BarDatabase : RoomDatabase() {
abstract fun barDao() : BarDao
}
val barDatabase = Room
.databaseBuilder(application, BarDatabase::class.java, "Bar.db")
.fallbackToDestructiveMigration()
.build()
data class FooWithBar(
@Embedded
val foo: Foo,
@Relation(
parentColumn = "barId",
entityColumn = "id"
)
val bar: Bar
)
如果 Foo
table 与 Bar
[=] 驻留在不同的数据库中,是否可以编写查询以获取连接模型 FooWithBar
41=]?
我知道如果我在同一数据库中同时拥有 Foo
和 Bar
实体,我可以在 dao 中编写一个查询,如:
@Query("SELECT * FROM foo")
suspend fun getFooWithBar() : FooBar?
并且编译器会根据注释生成一个 SQL 查询,该查询将通过 Foo.barId -> Bar.id
关系.
但我不知道是否可以在不同数据库中跨 table 进行这样的连接。
我知道如果我将这些 table 托管在同一个数据库中,我可以实现这一点,但我想保持我的数据库独立。
我想保持我的数据库独立的事实是否表明有“气味”?
是否依赖领域模型?如果是这样,什么时候将域模型拆分为不同的数据库有哪些好的经验法则?
Does the fact I want to keep my db's seperate indicate a "smell"?
是的,尤其是 Room。它引入了复杂性和低效率。
Is it possible to write a query where I can get the join model FooWithBar if the Foo table resides in a different database than the Bar table?
对于您的简单示例是的,但从示例中可以看出没有实际的 SQL JOIN(没有附加),即您获取 Foo 对象并通过获取适当的 Bar 来模拟 JOIN (或酒吧)。
如果您尝试混合来自不同数据库的实体,那么您将遇到问题,例如
即使添加(编辑)Dao 没有问题,例如:-
@Query("SELECT * FROM foo JOIN bar ON foo.barId = bar.id")
fun getAllFooWithBar(): List<FooWithBar>
根据(来自 Android Studio 的屏幕截图)看起来不错:-
当你编译时你会得到这样的错误:-
E:\AndroidStudioApps\SO67981906KotlinRoomDate\app\build\tmp\kapt3\stubs\debug\a\a\so67981906kotlinroomdate\FooDao.java:22: error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such table: bar)
public abstract void getAllFooWithBar();
^E:\AndroidStudioApps\SO67981906KotlinRoomDate\app\build\tmp\kapt3\stubs\debug\a\a\so67981906kotlinroomdate\FooDao.java:22: error: Not sure how to convert a Cursor to this method's return type (void).
public abstract void getAllFooWithBar();
在单个查询中获取需要来自两个数据库的表的任何内容将超出 Room 的范围,因为每个数据库只知道它自己的表。
但是,如果您将一个数据库附加到另一个数据库,那么您将在一个数据库中拥有两个数据库,但房间不理解它。所以你基本上必须恢复使用 SupportSQLiteDatabase(类似于使用原生 Android SQlite(但有一些限制))。
例子(很简单)
Foo实体
@Entity
data class Foo(
@PrimaryKey
val fooId: Long?,
val barId: Long,
val fooText: String
)
- 基本相同
FooDao
@Dao
interface FooDao {
@Insert
fun insert(foo: Foo): Long
@Query("SELECT * FROM foo")
fun getAllFoos(): List<Foo>
@Query("SELECT * FROM foo WHERE fooId=:fooId")
fun getFooById(fooId: Long): Foo
/* !!!!NO GO!!!!
@Query("SELECT * FROM foo JOIN bar ON foo.barId = bar.id")
fun getAllFooWithBar(): List<FooWithBar>
*/
}
- 一些简单的道
FooDatabase
@Database(
entities = [Foo::class],
version = 1,
exportSchema = false
)
abstract class FooDatabase : RoomDatabase() {
abstract fun fooDao() : FooDao
fun attachBar(context: Context): Boolean {
var rv: Boolean = false
if (instance != null) {
val dbs = this.openHelper?.writableDatabase
val barpath = context.getDatabasePath(BarDatabase.DBNAME)
if (dbs != null) {
dbs.execSQL("ATTACH DATABASE '$barpath' AS $BAR_SCHEMA_NAME")
rv = true
}
}
return rv
}
fun closeInstance() {
if(instance == null) return
if (this.isOpen()) {
this.close()
}
instance = null
}
companion object {
@Volatile
private var instance: FooDatabase? = null
fun getInstanceWithForceOption(context: Context, forceReopen: Boolean = false): FooDatabase {
if (forceReopen) instance?.closeInstance()
if (instance == null) {
instance = Room.databaseBuilder(context,FooDatabase::class.java, DBNAME)
.allowMainThreadQueries()
.addCallback(FOO_CALLBACK)
.build()
}
return instance as FooDatabase
}
fun getInstance(context: Context): FooDatabase {
return getInstanceWithForceOption(context, false)
}
val FOO_CALLBACK = object: RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
}
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
}
}
const val DBNAME: String = "foo.db"
const val BAR_SCHEMA_NAME = "bar_schema"
}
}
- 未使用回调,但如果始终通过附加访问,则可以在 onOpen 中完成附加
栏实体
@Entity
data class Bar(
@PrimaryKey
val id: Long?,
val barText: String
)
- 基本相同
BarDao
@Dao
interface BarDao {
@Insert
fun insert(bar: Bar): Long
@Query("SELECT * FROM bar")
fun getAllBars(): List<Bar>
@Query("SELECT * FROM Bar WHERE id=:id")
fun getBarById(id: Long): Bar
}
条形数据库
@Database(
entities = [Bar::class],
version = 1,
exportSchema = false
)
abstract class BarDatabase : RoomDatabase() {
abstract fun barDao() : BarDao
fun closeInstance() {
if (this.isOpen()) {
this.close()
}
instance = null
}
companion object {
@Volatile
private var instance: BarDatabase? = null
fun getInstanceWithForceOption(context: Context, forceReopen: Boolean = false): BarDatabase {
if (forceReopen) instance?.closeInstance()
if (instance == null) {
instance = Room.databaseBuilder(context,BarDatabase::class.java, DBNAME)
.allowMainThreadQueries()
.addCallback(BAR_CALLBACK)
.build()
}
return instance as BarDatabase
}
fun getInstance(context: Context): BarDatabase {
return getInstanceWithForceOption(context, false)
}
val BAR_CALLBACK = object: RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
}
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
}
}
const val DBNAME: String = "bar.db"
}
}
- 再次回调什么都不做
FooWithBar
class FooWithBar {
var foo: Foo
var bar: Bar
constructor(fooId: Long, fooDao: FooDao, barDao: BarDao) {
this.foo = fooDao.getFooById(fooId)
this.bar = barDao.getBarById(foo.barId)
}
}
- 由于您不能同时获得 Foo 和 Bar,这相当于通过 FooDatabase 获取 Foo 然后通过 BarDatabase 获取关联的 Bar 进行连接。
MainActivity 放在一起 :-
class MainActivity : AppCompatActivity() {
lateinit var foodb: FooDatabase
lateinit var fooDao: FooDao
lateinit var bardb: BarDatabase
lateinit var barDao: BarDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
foodb = FooDatabase.getInstance(this)
fooDao = foodb.fooDao()
bardb = BarDatabase.getInstance(this)
barDao = bardb.barDao()
/* Add some data */
fooDao.insert(Foo(null,barDao.insert(Bar(null,"BAR1")),"FOO1"))
barDao.insert(Bar(null,"BAR UNUSED"))
fooDao.insert(Foo(null,barDao.insert(Bar(null,"BAR2")),"FOO2"))
/* Get equivalent of join (simple) using the FooWithBar */
val allFoosWithBars = mutableListOf<FooWithBar>()
for(foo: Foo in fooDao.getAllFoos()) {
allFoosWithBars.add(FooWithBar(foo.fooId!!,fooDao,barDao))
}
for(fwb: FooWithBar in allFoosWithBars) {
Log.d("FOOBARINFO","Foo is ${fwb.foo.fooText} Bar is ${fwb.bar.barText}")
}
//* Done with the Bar database Room wise
bardb.closeInstance()
foodb.attachBar(this) //<<<<< ATTACHES the Bar database to the Foo
/* Get a Supprort SQLite Database */
var sdb = foodb.openHelper.writableDatabase
/* Query Foo and the attached Bar */
var csr = sdb.query("SELECT * FROM foo JOIN ${FooDatabase.BAR_SCHEMA_NAME}.bar ON foo.barId = ${FooDatabase.BAR_SCHEMA_NAME}.bar.id")
DatabaseUtils.dumpCursor(csr)
csr.close()
}
}
结果
2021-06-16 16:35:04.045 D/FOOBARINFO: Foo is FOO1 Bar is BAR1
2021-06-16 16:35:04.045 D/FOOBARINFO: Foo is FOO2 Bar is BAR2
2021-06-16 16:35:04.092 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@ee9871b
2021-06-16 16:35:04.093 I/System.out: 0 {
2021-06-16 16:35:04.093 I/System.out: fooId=1
2021-06-16 16:35:04.093 I/System.out: barId=1
2021-06-16 16:35:04.093 I/System.out: fooText=FOO1
2021-06-16 16:35:04.093 I/System.out: id=1
2021-06-16 16:35:04.093 I/System.out: barText=BAR1
2021-06-16 16:35:04.093 I/System.out: }
2021-06-16 16:35:04.093 I/System.out: 1 {
2021-06-16 16:35:04.093 I/System.out: fooId=2
2021-06-16 16:35:04.093 I/System.out: barId=3
2021-06-16 16:35:04.093 I/System.out: fooText=FOO2
2021-06-16 16:35:04.093 I/System.out: id=3
2021-06-16 16:35:04.093 I/System.out: barText=BAR2
2021-06-16 16:35:04.094 I/System.out: }
2021-06-16 16:35:04.094 I/System.out: <<<<<