Reputation: 24582
The root problem: I want to set the id of [Entity A] in foreign key of [Entity B] but id of [Entity A] is not available until inserted in the database (because it is autogenerated by the DBMS).
Using architecture components (Room, ViewModel and LiveData), how can I perform a transaction that saves multiple related entities in the database? The following code currently resides in the ViewModel and works fine. The problem is I want to put this AsyncTask
in the repository layer like other simple one-operation queries, but is it OK? Because in that case the repository would be responsible for managing relationships and knowing about entity details.
As I said above, the main problem is that I need id of the inserted entity so I can save it in another entity. If this requirement didn't exist, I would be able to persist each entity one by one in separate AsyncTask
s in the repository.
MainViewModel.java:
public void buy(Item item, Store store) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
long storeId = mRepository.insertStore(store);
Purchase purchase = new Purchase(storeId); // here uses id of the store
long purchaseId = mRepository.insertPurchase(purchase);
item.setPurchaseId(purchaseId); // here uses id of the purchase
mRepository.updateItem(item);
return null;
}
}.execute();
}
Upvotes: 3
Views: 1052
Reputation: 1257
You can execute multiple database operations in a transaction using Android Room. This way, you are ensured that your database integrity is not altered in case one of those operation fails (operations are rolled-back).
Here is how you can define a Transaction with Room in the Dao
class:
@Dao
public abstract class MyDao {
@Insert
public abstract long insertStore(Store store);
@Insert(onConflict = OnConflictStrategy.ROLLBACK)
public abstract long recordPurchase(Purchase purchase);
@Update
public abstract void updateItem(Item updatedItem);
@Transaction
public void buyItemFromStore(Item boughtItem, Store store) {
// Anything inside this method runs in a single transaction.
long storedId = insertStore(store);
Purchase purchase = new Purchase(storeId);
long purchaseId = recordPurchase(purchase);
item.setPurchaseId(purchaseId);
updateItem(item);
}
}
You can refer to the documentation for an explanation on how @Transaction
works.
Then in your repository class, call the buyItemFromStore
from your AsyncTask
:
public class MyRepository {
private MyDao dao;
public void buy(Item item, Store store) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doOnBackground(Void... voids) {
// Everything is saved in a transaction.
dao.buyItemFromStore(item, store);
return null;
}
}
}
}
Note that this is perfectly fine for the Repository
layer to be aware of relationships between entities, as long as the stored objects are related in some way (with Store Purchase and Item it seems to be the case).
If you are unable to alter your Dao
class, consider RoomDatabase.runInTransaction
.
Upvotes: 0
Reputation: 1190
I think what you're doing is fine if you keep this in the Repository layer. I don't think keeping this in the ViewModel is a good idea as it's suppose to be the Repository's responsibility to handle your data, in this case, the Item
and Store
objects. I believe that your Repository should be responsible for the management of this data and its relationships. To answer your question about receiving the ID of the updated entity, what you can do is have your AsyncTask implement the onPostExecute
method and have your doInBackground
method return an actual value (like the storeId
) instead of null
. You can then have onPostExecute
retrieve that value and delegate control to a callback listener of some sort.
Upvotes: 1