Reputation: 2196
I have "Parent" and "Child" hibernate entities.
On "Parent" I have a Set<Child>
to hold it's children.
When I update the Parent with new children, all works fine: the children are created on "child" table.
But, when I remove one element from the Parent hashset and save, the correspondent child on database is not be deleted.
Here is:
On PARENT (named Workflow):
@OneToMany(orphanRemoval=true, cascade = CascadeType.ALL, mappedBy="workflow", fetch = FetchType.EAGER)
private Set<ActivityDB> activities;
On Child (named Activity)
@ManyToOne
@JoinColumn(name="id_workflow")
@Fetch(FetchMode.JOIN)
private WorkflowDB workflow;
I'm working on persistent instance inside the session. No error is raised. Just seems to work fine, but the register on database still there.
To do a test, I load the Workflow and do a
workflow.activities.remove( activity_index_x )
and then save the workflow using session.update( workflow )
.
but the "activity_index_x" still in database and comes to life again when I reload the workflow.
Upvotes: 5
Views: 5235
Reputation: 153820
Make sure you go through the manual regarding bidirectional association links.
The best practices include adding the add/remove child methods:
class WorkflowDB {
public void remove (ActivityDB a) {
if (a != null) {
this.activities.remove(a);
a.setWorkflow(null);
}
}
public void add (ActivityDB a) {
if (a != null) {
this.activities.add(a);
a.setWorkflow(this);
}
}
}
But because you use a Set as the one-to-many side, you need to pay extra attention to equals and hashcode. The best way is to use a business-key for checking equality and for the hash-code algorithm, and never use the database identifier for equals/hashcode, especially in conjunction with hash-like data structures (set/map).
Bidirectional associations are more complicated to manage than unidirectional ones. If you don't really need the one-to-many side, you can remove it and replace it with a query instead. That way you'd have to manage only the many-to-one side.
Upvotes: 1
Reputation: 2417
This is caused by the child-to-parent reference not being cleared. Since you mapped both sides (and configured it this way) Hibernate will actually look at the child end of the relation.
The best way to fix this is to also clear the workflow field on the activity when you remove the activity from the workflow (and reversely), so:
class Workflow {
public void remove (Activity a) {
if (this.activities.remove(a)) {
a.setWorkflow(null);
}
}
public void add (Activity a) {
if (this.activities.add(a)) {
a.setWorkflow(this);
}
}
}
The main question is which side of the relation do you want to maintain the relation-state in? You could also map the relation on the Workflow (do not use the mappedBy attribute, but use a JoinTable annotation to keep the column on the child table) and only map the parent-Workflow as a read-only (insertable=false,updatable=false) field in the Activity. This way the Workflow is completely in control of which activities are part of it and the activities can still see the workflow they are part of.
class Workflow {
@OneToMany
@JoinTable(...)
private Set<Activity> activities
}
class Activity {
@Column(insertable=false, updatable=false)
private Workflow workflow
}
Upvotes: 1