Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate JPA: @OneToMany delete old, insert new without flush

I actually never quite understood this behavior in hibernate. I am using a @OneToMany relationship in a Entity called 'Parent', which is annotated like this:

@OneToMany(cascade = {CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, orphanRemoval = true) @JoinColumn(name = "entity_id", insertable = true, updatable = true, nullable = false) private List<Child> children; 

Now I want to do the following within one transaction:

  • Get the parent entity
  • iterate through the list of children
  • delete one of the children
  • insert a new child

So, basically I am just entirely replacing one of the children.

As far as I understand this problem, I should be able to do something like this: (please note that this is just some java pseudocode to illustrate the problem)

@TransactionAttribute(TransactionAttributeType.REQUIRED) public void deleteAndAdd(Long parentId, Long childId) {   Parent parent = entityManager.find(parentId);   for (Iterator it = parent.children.iterator(); it.hasNext();) {     Child child = it.next();     if (child.id == childId) {       it.remove();     }   }   Child newChild = new Child();   parent.children.add(newChild); } 

However, this fails in case the new Child has the same unique key values as the old one. So, basically it seems like the old child entity isn't removed properly, before the new one is persisted.

If I add a entityManager.flush() between deleting the old child and persisting the new child like this:

@TransactionAttribute(TransactionAttributeType.REQUIRED) public void deleteAndAdd(Long parentId, Long childId) {   Parent parent = entityManager.find(parentId);   for (Iterator it = parent.children.iterator(); it.hasNext();) {     Child child = it.next();     if (child.id == childId) {       it.remove();     }   }   entityManager.flush();   Child newChild = new Child();   parent.children.add(newChild); } 

Everything works fine. The child is deleted before the new one is inserted, as it should.

As I don't want to asume that hibernate mixes up the order of the statements that are sent to the DB, there must be something else I am assuming about hibernate which isn't the case. Any ideas why the latter example works, while the first one doesn't?

Hibernate version is 3.5. DB is Mysql InnoDB

like image 312
st-h Avatar asked Jul 01 '13 18:07

st-h


People also ask

How do you delete child records in JPA?

First, we'll start with CascadeType. REMOVE which is a way to delete a child entity or entities when the deletion of its parent happens. Then we'll take a look at the orphanRemoval attribute, which was introduced in JPA 2.0. This provides us with a way to delete orphaned entities from the database.

How do I delete a record in JPA?

For default JPA entities, you can delete data from the HCL Commerce database using the remove() method on the access bean's interface. If you want to delete your customized JPA entity, define a JPA DAO implementation that extends AbstractJPAEntityDaoImpl .

How do you delete child records when parent is deleted in Hibernate?

Then how do we delete the child entity from the database? Hibernate does that automatically when you set the orphanRemoval attribute of the @OneToMany annotation to true and the cascade attribute to CascadeType. ALL , it auto delete child entities while deleting parent. CascadeType.


2 Answers

Hibernate doesn't know about, nor respect, all database constraints (e.g. MySQL unique constraints). It's a known issue they don't plan on addressing anytime soon.

Hibernate has a defined order for the way operations occur during a flush.

Entity deletions will always happen after inserts. The only answers I know about are to remove the constraint or add the additional flush.

EDIT: By the way, the reason for the defined order is that this is the only way to guarantee foreign key constraints (one of the constraints they DO care about) aren't violated, even if the user does something out of order.

like image 78
Pace Avatar answered Sep 25 '22 17:09

Pace


For the sake of future readers, one way to resolve this issue is to use deferred constraints. PostgreSQL and Oracle support them, maybe other RDBMS' too. Hibernate will issue all statements within a transaction, and deferral will ensure that constraints are enforced upon transaction commit only. In PostgreSQL, for example:

ALTER TABLE company     ADD CONSTRAINT name_unique UNIQUE (name) DEFERRABLE INITIALLY DEFERRED; 

It is not ideal, but it is simple and effective.

like image 26
Nikša Baldun Avatar answered Sep 23 '22 17:09

Nikša Baldun