如何以干净的方式创建外键与 Room DB 相关的行和子项?
Posted
技术标签:
【中文标题】如何以干净的方式创建外键与 Room DB 相关的行和子项?【英文标题】:How can I create row and children related by ForeignKey with RoomDB in a clean way? 【发布时间】:2021-11-25 11:48:51 【问题描述】:这个问题在某种程度上与my last question有关,因为它是同一个项目,但现在我正努力向前迈出一步。
所以,在我之前的问题中,我只有一张桌子;这次我有两个表:新的第二个表应该包含第一个表的行的相关属性,在 OneToMany 关系中。因此,我在第二个表中存储了一个 ForeignKey,它将存储第一个表的相关行的 Row ID(显然)。
问题是这样的:目的是同时创建两个寄存器(父和子),使用相同的形式,而 ParentTable 使用 AUTO_INCREMENT 作为他的 PrimaryKey(AKA ID)。 p>
由于 RoomDb 的工作方式,我使用 POJO 进行创建:但在插入后,据我所知,此 POJO 不会给我自动生成的 ID...所以,我能做到的唯一解决方法想象一下,在提交表单时,首先为父级创建 INSERT,然后使用创建父级的表单字段之一进行某种“SELECT * FROM parent_table WHERE field1 LIKE :field1”,检索 ID,然后使用该 ID 创建子表的 POJO 并执行下一个 INSERT 操作。但是我觉得这种方法有些不对劲,上次我以这种方式实现类似的东西时,我最终得到了很多自定义侦听器和回调地狱(我仍然对此做噩梦)。
关于自定义侦听器这件事,这是我最终为不同项目选择的不同问题的解决方案(有关此问题的更多详细信息,请参见old question)。看看那个老问题可能有助于添加一些关于我在 MVVM 架构中被误导的背景。但是,请注意当前的问题与 WebServices 无关,因为数据库纯粹是手机应用程序中的本地,没有外部来源。
但是,我想知道:这不是矫枉过正吗(我的意思是INSERT parent -> SELECT parentID -> INSERT child
的东西)?这样做是不可避免的,还是有更干净的方法?
我的 Repository 类中的“创建方法”如下所示:
public void insertParent(Parent parent)
new InsertParentAsyncTask(parent_dao).execute(parent);
private static class InsertParentAsyncTask extends AsyncTask<Parent, Void, Void>
private final ParentDao parent_dao;
private InsertParentAsyncTask(ParentDao parent_dao)
this.parent_dao = parent_dao;
@Override
protected Void doInBackground(Parent... parents)
parent_dao.insert(parents[0]);
return null;
试图按照马里奥的回答,我在我父母的 DAO 中改变了这个方法:
// OLD
@Insert
void insert(Parent parent);
// NEW (yes, I use short type for parent's ID)
@Insert
short insert(Parent parent);
EDIT2: 现在,我正在尝试对我的存储库的插入 AsyncTask 进行更改,如下所示:
private static class InsertParentAsyncTask extends AsyncTask<Parent, Void, Short>
private final ParentDao parent_dao;
private InsertParentAsyncTask(ParentDao parent_dao)
this.parent_dao = parent_dao;
@Override
protected Short doInBackground(Parent... parents)
short parent_id;
parent_id = parent_dao.insert(parents[0]);
return parent_id;
@Override
protected void onPostExecute(Short hanzi_id)
// TODO ??? What now?
长话短说
它在这里对我有用,但这不是干净的代码(显然):
// TODO I am aware that AsyncTask is deprecated
// My Repository class uses this
public void insertParentAndChildren(Parent parent, String[] children_list)
new InsertParentAndChildrenAsyncTask(parent_dao, children_list).execute(parent);
private static class InsertParentAndChildrenAsyncTask extends AsyncTask<Parent, Void, Short>
private final ParentDao parent_dao;
private String[] children_list;
private InsertParentAndChildrenAsyncTask(ParentDao parent_dao, String[] children_list)
this.parent_dao = parent_dao;
this.children_list = children_list;
@Override
protected Short doInBackground(Parent... parents)
short parent_id;
Long row_id = parent_dao.insert(parents[0]);
parent_id = parent_dao.getIdForRowId(row_id);
return parent_id;
@Override
protected void onPostExecute(Short parent_id)
// Second "create method" for children
for (int n = 0; n < children_list.length; n++)
Child child = new Child();
child.setParentId( parent_id );
child.setMeaning( children_list[n] );
// My Repository has this method as well
insertChildStaticMethod(child);
【问题讨论】:
【参考方案1】:你在正确的轨道上。一种干净的方法是将其包装在这样的函数中:
fun saveParent(parent: Parent): Int
val rowId = parentDao.insert(parent) // Returns Long rowId
val parentId = parentDao.getIdForRowId(rowId) // SELECT id FROM table_parent WHERE rowid = :rowId
return parentId
这个函数可以成为存储库类的一部分,使其更加干净。 您在 DAO 中的函数可以像这样返回 rowId 和 Parent.ID:
@Insert
fun insert(parent: Parent): Long
@Query("SELECT ID FROM table_parent WHERE rowid = :rowId")
fun getIdForRowId(rowId: Long): short
如果您想首先使用基本功能,您可以在使用allowMainThreadQueries()
构建数据库时在主线程上调用 Room 数据库函数:
MyApp.database = Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
像这样,您可以将后台处理推迟到以后。如果您对该主题有具体问题,最好单独提出问题。
【讨论】:
哦,Kotlin(太可爱了,但我仍然使用 Java :P)。无论如何,我想看看你的答案,DAO 的创建方法能够返回一个 ID?很高兴知道...我必须尝试一下 既然你喜欢“干净”,那就去 Kotlin 吧!你永远不会回头。 :) 查看我的编辑以从 insert 方法中获取 rowId。 我猜Kotlin更简洁毫无疑问......但是这只老狗(我是说我)仍然害怕无法适应Kotlin(同样的借口拖延学习Kotlin 2年已经)......无论如何你有一点,Kotlin 的样板代码比 Java 少,这是肯定的...... 房间插入不返回父id/主键而是rowid,查看这里:developer.android.com/training/data-storage/room/accessing-data。 对于基本功能,可以考虑使用 Room allowMainThreadQueries(),因此不需要 asyncTask 或协程。然后您可以稍后决定如何获取主线程的数据库访问功能。不,Java 中不存在协程/挂起函数。相反,Java 有“回调地狱”;)。【参考方案2】:我认为您可以尝试SELECT last_insert_rowid()
作为房间查询(您可以这样编写,无需引用任何表)。此语句返回上次插入数据库的 rowid。默认情况下,当您将 rowId 定义为自动增量整数时,大多数 sql DB 将它们用作主键。所以我猜你会在你的 DAO 中定义以下方法
@Query("SELECT last_insert_rowid()")
public abstract int getLastId()
@Insert
void insert(Parent parent)
然后您可以在事务中将它与您的插入语句一起使用。像这样
@Transaction
public int insertAndGetPrimaryKey(Parent parent)
insert(parent);
return getLastId();
使用事务很重要,否则如果在您的应用中多个线程可能同时修改表,则传递的 id 可能是错误的。
顺便说一句,我不会使用短作为主键。不仅 XD 短(只有 32k 容量),而且还真的要使用 Integer(40 亿容量)。如果这些是 80 年代的 id 全部用于它(那些是你在所有 XD 之后保存的 2 个完整字节)但现在一天的内存既便宜又丰富,正如我所说的整数是 DB 默认使用的。
【讨论】:
令人印象深刻!单线程和多线程 Android Java 代码的干净解决方案!使用 DB 默认的主键也是清理代码问题的一个好点。 乐于助人!! 这对你有用吗?有一个替代方法,您可以按 rowId 以 desc 顺序对父表进行排序,并限制到第一行。但它在功能上应该是完全相同的代码,您仍然希望使用该事务。唯一的区别是它允许您使用任何其他类型作为主键(在我看来这并不是那么有用) ...我想就此向您提供反馈,但遗憾的是我还没有时间重构。我几乎不能每天花一两个小时来编码(我喜欢编码的日子,因为我每周工作 9,5 小时每周 6 天(本周 7 天 T_T)......现在,我的编码时间去了除了重构之外的任何事情...... T_T 但是,我知道数据库事务及其用途;因此,即使您的代码尚未经过测试,我也知道它会起作用。以上是关于如何以干净的方式创建外键与 Room DB 相关的行和子项?的主要内容,如果未能解决你的问题,请参考以下文章