RoomDB-您可以连接来自不同数据库的表吗 [英] RoomDB - Can you JOIN tables from different databases

查看:33
本文介绍了RoomDB-您可以连接来自不同数据库的表吗的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道RoomDB是否支持来自不同数据库的两个表的联接。

假设我有以下实体和数据库:

@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表与Bar表位于不同的数据库中,是否可以编写可以获取联接模型FooWithBar的查询?

我知道如果我在同一个数据库中同时有FooBar实体,我可以在DAO中编写如下查询:

@Query("SELECT * FROM foo")
suspend fun getFooWithBar() : FooBar?

编译器将根据注释生成一个SQL查询,该查询将通过Foo.barId -> Bar.id关系连接两个表(FooBar)。

但我不知道是否可以跨不同数据库中的表执行这样的联接。

我知道如果我将这些表托管在同一个数据库中,我可以做到这一点,但我希望将我的数据库分开。

我要保留数据库的隔离是否表示有气味(&Q;&Q;)?

是否取决于域模型?如果是这样的话,对于何时将域模型分成不同的数据库,有什么好的经验法则呢?

推荐答案

我要保留数据库的隔离是否表示有气味(&Q;&Q;)?

是的,特别是在房间里。它引入了复杂性和低效。

如果Foo表驻留在与Bar表不同的数据库中,是否可以编写可以获取连接模型FooWithBar的查询?

对于您的简单示例是的,但是从示例中可以看出,您没有使用实际的SQL连接(没有附加),也就是说,您获得了foo对象并通过获取适当的条(或多个条)来模拟连接。

如果您尝试混合来自不同数据库的实体,则会遇到问题,例如

即使添加(在编辑时)DAO没有问题,例如:-

@Query("SELECT * FROM foo JOIN bar ON foo.barId = bar.id")
fun getAllFooWithBar(): List<FooWithBar>

根据(Android Studio截图):-

编译时会出现如下错误:-

E:AndroidStudioAppsSO67981906KotlinRoomDateappuild	mpkapt3stubsdebugaaso67981906kotlinroomdateFooDao.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:AndroidStudioAppsSO67981906KotlinRoomDateappuild	mpkapt3stubsdebugaaso67981906kotlinroomdateFooDao.java:22: error: Not sure how to convert a Cursor to this method's return type (void).
    public abstract void getAllFooWithBar();

在单个查询中获取需要两个数据库中的表的任何内容将超出Room的作用域,因为每个数据库只知道自己的表。

但是,如果您将一个数据库附加到另一个数据库,那么您将在一个数据库中同时拥有两个数据库,但是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>
     */
}
  • 一些简单的DAO

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"
    }
}
  • 如果始终通过Attach访问,则不使用回调,但可以在onOpen中执行Attach

条形图实体

@Entity
data class Bar(
    @PrimaryKey
    val id: Long?,
    val barText: String
)
  • 基本相同

条刀

@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
}

BarDatabase

@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来执行联接。

主要活动总而言之:-

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-您可以连接来自不同数据库的表吗的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆