Search code examples
javaandroidrelational-databaseandroid-roomauto-increment

How can I create row and children related by ForeignKey with RoomDB in a clean way?


This question is somehow related to my last question, because it is the same project but now I am trying to go one more step forward.

So, in my previous question I only had one table; this time I have two tables: the new second table is supposed to contain related attributes for the rows of the first table, in a OneToMany relationship. So, I store a ForeignKey in the second table that would store the Row ID of the first table's related row (obviously).

The problem is this: the intention is creating both registers (parent and child) at the same time, using the same form, and ParentTable uses AUTO_INCREMENT for his PrimaryKey (AKA ID).

Due to how RoomDb works, I do the creation using a POJO: but after insertion, this POJO won't give me the auto-generated ID as far as I know... so, the only workaround I am able to imagine is, when submitting the form, first make the INSERT for the parent, then using one of the form's fields that created the parent to make some kind of "SELECT * FROM parent_table WHERE field1 LIKE :field1", retrieving the ID, and then use that ID to create the child table's POJO and perform the next INSERT operation. However I feel something's not right about this approach, the last time I implemented something similar this way I ended up with a lot of Custom Listeners and a callback hell (I still have nightmares about that).

About the Custom Listeners thing, it is the solution I ended up choosing for a different problem for a different project (more details about it in this old question). Taking a look to that old question might help adding some context about how misguided I am in MVVM's architecture. However, please notice the current question has nothing to do with WebServices, because the Database is purely local in the phone's app, no external sources.

However, I am wondering: isn't this overkill (I mean the INSERT parent -> SELECT parentID -> INSERT child thing)? Is it inevitable having to do it this way, or there is rather a more clean way to do so?


The "create method" in my Repository class looks like this:

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

Trying to follow Mario's answer, I changed this method in my parent's DAO:

// OLD
@Insert
void insert(Parent parent);

// NEW (yes, I use short type for parent's ID)
@Insert
short insert(Parent parent);

EDIT2: Now, I am trying to make changes to my Repository's insert AsyncTask, like this:

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

LONG STORY SHORT

It worked for me this way down here, but this ain't clean code (obviously):

// 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);
        }
    }
}

Solution

  • I think you could try SELECT last_insert_rowid() as a query on room (you write it just like that no need to reference any table). This statement returns the rowid of the last insertion into your database. By default rowId is what most sql DBs use as primary keys when you define them as auto incremental integers. so I guess you would define the following method in your DAO

    @Query("SELECT last_insert_rowid()")
    public abstract int getLastId()
    
    @Insert
    void insert(Parent parent)
    

    then you can use it together with your insert statement in a transaction. like so

    @Transaction
    public int insertAndGetPrimaryKey(Parent parent){
        insert(parent);
        return getLastId();
    }
    

    it is important to use transaction as else the id delivered could be the wrong one if in your app multiple threads could potentially modify the tables at the same time.

    btw I would not use short for a primary key. not only is it short XD (only 32k capacity) but the satndard is really to use Integer (4 bn capacity). if these were the 80's id be all for it (those are 2 entire bytes that you are saving after all XD) but now a days memory is cheap and abundant and as i said integer is what DBs work with by default.