Cannot prevent second commit after programmatically persisting many-to-many join table entity

Date: 30.04.2020
CUBA Experience: just beginning
JPQL Experience: just beginning
Operating System: MacOS 10.15.4 (19E287)
File System: Case-Sensitive Journaled HFS+
CUBA Platform version: 7.2.3
CUBA Studio plugin version: 13.1-191
IntelliJ version: IntelliJ IDEA 2019.3.4 (Ultimate Edition)
Example Project: mtmexample.zip (14.3 MB)

Use Case: In a many-to-many association, utilizing 3 separate entities/tables (Player, Membership, Team), I want to add a new Player entity and also create and add a new Team entity/entities for this new Player using the normal screen and view logic.

Background: In the “cuba-petclinic-data-model-many-to-many-master” example, if I try to create and add a new Pet while also creating and adding a new Insurance Company for this new Pet, I receive the following error:

“IllegalStateException: An attempt to save an entity with reference to some not persisted entity. All newly created entities must be saved in the same transaction. Put all these objects to the CommitContext before commit.”

I also received this error when I tried my first many-to-many example project. And I found several examples of how this problem should be fixed but I was not able to implement these solutions because most of them refer to deprecated API’s from CUBA version 6.x.

Result: I therefore decided to create a second example, to find my own solution. With my sample project “mtmexample” (attached), I was able to eliminate this error with the following code in PlayEdit.java:

@Inject
private DataManager dataManager;

@Subscribe
public void onBeforeCommitChanges(BeforeCommitChangesEvent event) {
CommitContext commitContext = new CommitContext(playerDc.getItem());
for (Membership member : memberDc.getMutableItems()) {
commitContext.addInstanceToCommit(member);
}

dataManager.commit(commitContext);
}

Therefore, with this code I can create a new Player and also create one or more new Teams and the corresponding Membership (Join Table) and save all of these new entities to the database. If I manually inspect the database, I see all three entities, Player, Membership and Team.

But after this code is executed it appears that the screen logic tries to perform its own normal commit, which is now a second commit, and this results in the following error in a red UI notification:

“Unique constant violation occurred (MTMEXAMPLE_PLAYER.PRIMARY)”

I assume that this error is caused because the primary key for the Player entity is already in use, respectively, already persisted to the database.

If I close this error message, discard the changes (Don’t save) in the dialog, return to the Player browser screen, and refresh the Player browser screen, then I see that changes were saved. And if I edit the new Player, that I just created, I also see the new Team(s) that was created. Therefore, it appears that all of the entities are correctly fetched to the screens after my initial commit.

I have tried using the following code (currently removed from PlayEdit.java) to eliminate the second commit:

@Subscribe(target = Target.DATA_CONTEXT)
public void onPreCommit(DataContext.PreCommitEvent event) {
event.preventCommit();
}

But this causes the UI to ask whether or not I want to discard my changes (which were already persisted by my code) and this is not the desired behavior.

I thought that such a many-to-many situation would be very easy to implement but apparently I am misunderstanding some basic behavior or applying my code in the wrong place, or both.

Does anyone know how to properly solve this in the client tier using my example?

Many thanks in advance.

Best regards
Chris

Hi @chrisbeaham4523,

I’m attaching your project with minor changes I’ve done to get it working:
mtmexample.zip (88.5 KB)

I suggest you use the gradle task zipProject when you want to share your project with others (notice that my attachment is 88.5KB, while yours is 14.3MB).

image

What I changed:

  1. Removed onBeforeCommitChanges method.
  2. Changed method createMembershipForTeam:
private Membership createMembershipForTeam(Team team) {
	Membership membership = dataContext.create(Membership.class);
	membership.setPlayer(playerDc.getItem());
	membership.setTeam(team);
	return membership;
}

When you create programatically an entity, you need to merge it into the datacontext. So instead of creating a new entity using metadata.create() you can use dataContext.create() and it will be done automatically:
image

Regards,
Peterson.

1 Like

Good Morning @peterson.br

Thank you very much for your efforts, the correction and the gradle tip; you saved my weekend! I hope that I can return the favor sometime in the future.

Best regards
Chris

1 Like