I have these entities
class Foo{
Set bars;
}
class Bar{
Foo parent;
String localIdentifier;
}
With this mapping (sorry,
If you are using oracle, you could also use deferrable constraints to postpone the checking of the constraints until the transaction is committed. Not sure if/how this is supported by other databases.
I guess there is no alternative to flushing
From here:
Hibernate is violating a unique constraint!
Hibernate isn't quite as clever with unique constraints as it is with foreign keys. Sometimes you might need to give a little hint.
A unique constraint violation could occur if two objects are both being updated, one is "releasing" a value and the other is "obtaining" the same value. A workaround is to flush() the session manually after updating the first object and before updating the second.
(This kind of problem occurs rarely in practice.)
I had a slightly similar issue, where I had a unique index on the child table on certain condition. So my issue was when I remove a record that satisfied this condition, then add another record that will restore that condition back, I got the UNIQUE INDEX issue.
After going through many trials and suggestions. I didn't like the idea of flushing after deleting and before the adding, I assume it will work though, I went to a constraint solution and dropped the unique index (as I didn't really needed the columns to be indexed) which did work:
(On postgres database)
ALTER TABLE bar ADD CONSTRAINT bar_uc
EXCLUDE (parent_id WITH =) WHERE (condition = true) INITIALLY DEFERRED;
INITIALLY DEFERRED is the key here.
Sample code that I was using:
//Transaction 1
Foo parent = new Foo('some-id');
boolean condition = true;
Bar c = new Bar('some-id', parent, condition);
parent.getChildren().add(c);
fooService.save(parent); //children list are cascaded
... later on
//Transaction 2
boolean condition = true;
Foo parent = fooSerice.findById('some-id'); //children list is eagerly fetched
Bar c1 = parent.getChildren().stream().filter(c -> c.getId().equals("some-id")).findFirst().get();
Bar c2 = new Bar('some-id1', parent, condition);
parent.getChildren().remove(c1);
parent.getChildren().add(c2);
If you want to avoid flushing the session here, try to replace the whole list (new List<Bar>()
instead of Clear()
). Hibernate should actually remove all the items in one shot before adding new. Just a try, not sure if it works.