如何以干净的方式创建行和 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 个完整字节)但现在内存便宜且丰富,正如我所说,整数是数据库默认使用的。