I've encountered a strange problem in a simple Spring Boot application, where I update a parent entity, delete all of its children and then create new ones.
After saving, the resulting entity looks fine and has all of the new children, but when I query it again, I find that one of the child entities is lost!
The quick and dirty solution for this would be to split the code into two transactions, but I want to understand the cause the orphan removal to act like this.
Here's the service code:
@Service
public class ParentService {
private final EntityManager em;
public ParentEntity getParent(UUID parentId) {
return em.createQuery(
"SELECT p "
"from ParentEntity p "
"JOIN FETCH p.children "
"WHERE p.id = :parentId", ParentEntity.class)
.setParameter("parentId", parentId)
.getSingleResult();
}
@Transactional
public ParentEntity resetChildren(UUID parentId) {
var parent = getParent(parentId);
parent.getChildren().clear();
addChildren(parent, 2);
em.persist(parent);
return parent;
}
private void addChildren(ParentEntity parent, int childCount) {
for (var i = 0; i < childCount; i ) {
parent.addChildren(new ChildEntity());
}
}
}
The SQL output after resetting children and the fetching the parent again is this:
select parententi0_.id as id1_1_0_, children1_.parent_id as parent_i2_0_1_, children1_.id as id1_0_1_, children1_.id as id1_0_2_, children1_.parent_id as parent_i2_0_2_ from parent parententi0_ left outer join child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?
insert into child (parent_id, id) values (?, ?)
insert into child (parent_id, id) values (?, ?)
delete from child where id=?
delete from child where id=?
delete from child where id=? <-- One extra delete
select parententi0_.id as id1_1_0_, children1_.parent_id as parent_i2_0_1_, children1_.id as id1_0_1_, children1_.id as id1_0_2_, children1_.parent_id as parent_i2_0_2_ from parent parententi0_ left outer join child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?
The entities look like this:
The parent
@Entity
@Table(name = "parent")
public class ParentEntity {
@Id
@GeneratedValue
private UUID id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<ChildEntity> children = new HashSet<>();
...
public void addChildren(ChildEntity child) {
this.children.add(child);
child.setParent(this);
}
...
}
And the child
@Entity
@Table(name = "child")
public class ChildEntity {
@Id
@GeneratedValue
private UUID id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}
An important note to this, that in my use case, the id of these entities is a UUID. I can't get it working with any numeric ids
The code repository with a unit test can be found here
An interesting thing happens, if I decide to add 1 child, instead of two or more, the parent itself is deleted! Because of this, it feels like I'm looking at a bug in Hibernate.
CodePudding user response:
The problem was that I had cascade on the child side, which lead to very very weird results. I don't want parent to be deleted on cascade, so I don't need that.
The fix was to change the child to this:
@Entity
@Table(name = "child")
public class ChildEntity {
@Id
@GeneratedValue
private UUID id;
@ManyToOne // <---- No more cascade!
@JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}