Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two way cascade fails on missing isRegisteredBean #3208

Open
tbee opened this issue Aug 29, 2023 · 2 comments
Open

Two way cascade fails on missing isRegisteredBean #3208

tbee opened this issue Aug 29, 2023 · 2 comments
Labels

Comments

@tbee
Copy link
Contributor

tbee commented Aug 29, 2023

I would like to confirm that the behaviour we are seeing is expected. We have the following setup:

@Entity
public class ActivityGroup {

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "activityGroup")
    private List<Activity> activities;
}

@Entity
public class Activity {

    @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    private ActivityGroup activityGroup;
}

Note the two way relations AND the cascade both ways. We know it should not be done this way, but as they say "it works under 7" and I'm afraid code may somehow depend on that behaviour.

Expected behavior

Under EBean 7 updating the 'status' attribute of a set of Activities works fine.

Actual behavior

Under EBean 12 an SQL update is made with a wrong version.

Steps to reproduce

Under EBean 12 we see the following SQL (all activities have version 2):

update activity set updatedAt=?, status=?, version=? where objectId=? and version=?; -- bind(Tue Aug 29 13:01:39 CEST 2023,Inactive,3,470,2)
update activity set updatedAt=?, version=? where objectId=? and version=?; -- bind(Tue Aug 29 13:01:39 CEST 2023,4,470,3)
update activity set updatedAt=?, status=?, version=? where objectId=? and version=?; -- bind(Tue Aug 29 13:01:39 CEST 2023,Inactive,4,471,3)

The first statement is the update of the first Activity 470. Through the AssocOne ActivityGroup is examined, and through its AssocMany all Activities, but because all are not dirty or are registered, they are skipped. Correct.

Next the second Activity 471 is supposed to be updated. Prior to the actual update its AssocOne ActivityGroup is checked, and cascading its AssocMany Activities: 470 is dirty, so this causes the second update statement. The 471 is the next in the AssocMany and it is checked as well. The code arrives in DefaultPersister.update(PersistRequestBean<?> request) and there the isRegisteredBean() check determines 471 is not registered, even though 471 is the bean being saved. This causes the third statement.

The third statement updates 471 from version 3 to 4, but it in the database is still at version 2 (the actual 'set status' update has not executed yet). This fails with a version conflict. So the bean is processed, the version was incremented, but it was not marked as registered.

This is the stack trace when 471 gets its AssocMany update:

update:487, DefaultPersister (io.ebeaninternal.server.persist) [3]
saveRecurse:448, DefaultPersister (io.ebeaninternal.server.persist)
saveRecurse:431, DefaultPersister (io.ebeaninternal.server.persist)
saveAllBeans:186, SaveManyBeans (io.ebeaninternal.server.persist)
processDetails:136, SaveManyBeans (io.ebeaninternal.server.persist)
saveAssocManyDetails:109, SaveManyBeans (io.ebeaninternal.server.persist)
save:82, SaveManyBeans (io.ebeaninternal.server.persist)
saveMany:893, DefaultPersister (io.ebeaninternal.server.persist)
saveAssocMany:887, DefaultPersister (io.ebeaninternal.server.persist)
update:500, DefaultPersister (io.ebeaninternal.server.persist) [2]
saveRecurse:448, DefaultPersister (io.ebeaninternal.server.persist)
saveRecurse:431, DefaultPersister (io.ebeaninternal.server.persist)
saveAssocOne:1066, DefaultPersister (io.ebeaninternal.server.persist)
update:491, DefaultPersister (io.ebeaninternal.server.persist) [1]
update:380, DefaultPersister (io.ebeaninternal.server.persist)
save:399, DefaultPersister (io.ebeaninternal.server.persist)
save:1637, DefaultServer (io.ebeaninternal.server.core)
save:1629, DefaultServer (io.ebeaninternal.server.core)
save:373, DB (io.ebean)

Should this scenario execute correctly?
Or should the status update have executed already?
Or should the isRegisteredBean have detected it is already being processed?

Even though the second update for 470 is technically correct, it is unnecessary.

@rbygrave
Copy link
Member

Can you create a test case for this? Perhaps based on https://github.com/ebean-orm-examples/example-minimal ?

@tbee
Copy link
Contributor Author

tbee commented Sep 20, 2023

We have patched this by disabling batching. When we are upgrading to Ebean 13 we'll revisit this. Ok?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants