如何以干净的方式创建行和 children 与外键相关的 Room DB?
How can I create row and children related by ForeignKey with RoomDB in a clean way?
这个问题与my last question有某种关系,因为它是同一个项目,但现在我想再向前迈进一步。
因此,在我之前的问题中,我只有一个 table;这次我有两个 table:新的第二个 table 应该包含 OneToMany 关系中第一个 table 行的相关属性。因此,我在第二个 table 中存储了一个 ForeignKey,它将存储第一个 table 相关行的行 ID(显然)。
问题是这样的: 目的是同时创建两个寄存器(parent 和 child),使用相同的形式,并且ParentTable 使用 AUTO_INCREMENT 作为他的主键(又名 ID)。
由于 RoomDb 的工作方式,我使用 POJO 进行创建:但插入后,据我所知,此 POJO 不会给我 auto-generated ID...所以,唯一的解决方法我可以想象的是,在提交表单时,首先为 parent 进行插入,然后使用创建 parent 的表单字段之一进行某种“SELECT * FROM parent_table WHERE field1 LIKE :field1",检索 ID,然后使用该 ID 创建 child table 的 POJO 并执行下一个 INSERT 操作。但是我觉得这种方法有些不对劲,上次我以这种方式实现类似的东西时,我最终得到了很多自定义监听器和回调地狱(我仍然对此做噩梦)。
关于自定义侦听器,这是我最终为不同项目的不同问题选择的解决方案(更多详细信息请参见 old question)。查看那个老问题可能有助于添加一些关于我在 MVVM 架构中被误导的上下文。但是,请注意当前的问题与 WebServices 无关,因为数据库在 phone 的应用程序中是纯本地的,没有外部来源。
但是,我想知道:这不是矫枉过正(我指的是 INSERT parent -> SELECT parentID -> INSERT child
的事情)吗?是不是必须这样做table,还是有更简洁的方法?
我的存储库 class 中的“创建方法”如下所示:
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;
}
}
为了遵循 Mario 的回答,我在 parent 的 DAO 中更改了这个方法:
// OLD
@Insert
void insert(Parent parent);
// NEW (yes, I use short type for parent's ID)
@Insert
short insert(Parent parent);
编辑2:
现在,我正在尝试更改我的存储库的插入 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);
}
}
}
你走在正确的轨道上。一种干净的方法是将它包装在这样的函数中:
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
}
此函数可以作为存储库的一部分 class 以使其更加干净。
您在 DAO 中的函数可以像这样 return 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()
像这样,你可以把后台处理推迟到以后。如果您对该主题有具体问题,最好提出一个单独的问题。
我想你可以尝试 SELECT last_insert_rowid()
作为房间查询(你就这样写,不需要引用任何 table)。这个语句returns最后一次插入你的数据库的rowid。默认情况下,当您将 rowId 定义为自动增量整数时,大多数 sql 数据库用作主键。所以我猜你会在你的 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();
}
使用事务很重要,否则如果在您的应用中多个线程可能同时修改 tables,则传递的 ID 可能是错误的。
顺便说一句,我不会使用 short 作为主键。不仅是短 XD(只有 32k 容量),而且 satndard 真的要使用 Integer(40 亿容量)。如果这些都是 80 年代的 id 就够了(这些是你在所有 XD 之后保存的 2 个完整字节)但现在内存便宜且丰富,正如我所说,整数是数据库默认使用的。
这个问题与my last question有某种关系,因为它是同一个项目,但现在我想再向前迈进一步。
因此,在我之前的问题中,我只有一个 table;这次我有两个 table:新的第二个 table 应该包含 OneToMany 关系中第一个 table 行的相关属性。因此,我在第二个 table 中存储了一个 ForeignKey,它将存储第一个 table 相关行的行 ID(显然)。
问题是这样的: 目的是同时创建两个寄存器(parent 和 child),使用相同的形式,并且ParentTable 使用 AUTO_INCREMENT 作为他的主键(又名 ID)。
由于 RoomDb 的工作方式,我使用 POJO 进行创建:但插入后,据我所知,此 POJO 不会给我 auto-generated ID...所以,唯一的解决方法我可以想象的是,在提交表单时,首先为 parent 进行插入,然后使用创建 parent 的表单字段之一进行某种“SELECT * FROM parent_table WHERE field1 LIKE :field1",检索 ID,然后使用该 ID 创建 child table 的 POJO 并执行下一个 INSERT 操作。但是我觉得这种方法有些不对劲,上次我以这种方式实现类似的东西时,我最终得到了很多自定义监听器和回调地狱(我仍然对此做噩梦)。
关于自定义侦听器,这是我最终为不同项目的不同问题选择的解决方案(更多详细信息请参见 old question)。查看那个老问题可能有助于添加一些关于我在 MVVM 架构中被误导的上下文。但是,请注意当前的问题与 WebServices 无关,因为数据库在 phone 的应用程序中是纯本地的,没有外部来源。
但是,我想知道:这不是矫枉过正(我指的是 INSERT parent -> SELECT parentID -> INSERT child
的事情)吗?是不是必须这样做table,还是有更简洁的方法?
我的存储库 class 中的“创建方法”如下所示:
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;
}
}
为了遵循 Mario 的回答,我在 parent 的 DAO 中更改了这个方法:
// OLD
@Insert
void insert(Parent parent);
// NEW (yes, I use short type for parent's ID)
@Insert
short insert(Parent parent);
编辑2: 现在,我正在尝试更改我的存储库的插入 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);
}
}
}
你走在正确的轨道上。一种干净的方法是将它包装在这样的函数中:
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
}
此函数可以作为存储库的一部分 class 以使其更加干净。 您在 DAO 中的函数可以像这样 return 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()
像这样,你可以把后台处理推迟到以后。如果您对该主题有具体问题,最好提出一个单独的问题。
我想你可以尝试 SELECT last_insert_rowid()
作为房间查询(你就这样写,不需要引用任何 table)。这个语句returns最后一次插入你的数据库的rowid。默认情况下,当您将 rowId 定义为自动增量整数时,大多数 sql 数据库用作主键。所以我猜你会在你的 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();
}
使用事务很重要,否则如果在您的应用中多个线程可能同时修改 tables,则传递的 ID 可能是错误的。
顺便说一句,我不会使用 short 作为主键。不仅是短 XD(只有 32k 容量),而且 satndard 真的要使用 Integer(40 亿容量)。如果这些都是 80 年代的 id 就够了(这些是你在所有 XD 之后保存的 2 个完整字节)但现在内存便宜且丰富,正如我所说,整数是数据库默认使用的。