0

I have

public class RelationObject  {
    @OneToMany(orphanRemoval = true, mappedBy = "relationObject")
    private Set<RelationParticipant> participants = new HashSet<RelationParticipant>();
}

public class BusinessObject  {
    @OneToMany(orphanRemoval = true, mappedBy = "businessObject")
    private Set<RelationParticipant> participants = new HashSet<RelationParticipant>();
}

and

public class RelationParticipant {
    @ManyToOne
    @JoinColumn(name = "ro_id", nullable = false)
    private RelationObject relationObject;

    @ManyToOne
    @JoinColumn(name = "bo_id", nullable = false)
    private BusinessObject businessObject;
}

And I have a RelationParticipant connected to one RelationObject (relobj) and one BusinessObject. Now I do em.remove(relobj), and on commit or flush I get an integrity exception. Or sometimes I don't, it depends.

According to JPA spec, "If the remove operation is applied to a managed source entity, the remove operation will be cascaded to the relationship target". But sometimes that just does not happen. And sometimes does. Why?

Maksim Gumerov
  • 642
  • 8
  • 23

2 Answers2

0

JPA states that orphan removal will operate the same as the cascade remove on a relationship, so there is no need to set cascade remove - when you remove a RelationObject, JPA will remove any referenced RelationParticipants if orphanRemoval = true.

You might not see this behavior in all cases because JPA can only cascade the removal to entities it knows about. In your case, check that the RelationObject's participants collection really holds 2 RelationParticipants that reference it in the database. If it does not, JPA will only remove the ones it knows about. A quick way to check is to call refresh on the RelationObject before calling remove on it. This is common in applications that do not maintain both sides of bidirectional relational relationships, for instance, by setting the RelationParticipants->RelationObject reference without also adding the RelationParticipants to the RelationObject's participants collection. If this is the case, JPA does not maintain bidirectional relationships for you - the application is required to keep both sides in synch itself.

Chris
  • 20,138
  • 2
  • 29
  • 43
  • Thanks, but could you please indicate where JPA spec reads like "when you remove a RelationObject, JPA will remove any referenced RelationParticipants if orphanRemoval = true"? The sources I consulted (not JPA, though) do not guarantee that. They guarantee cascading only for enities that become disconnected, like if I do relobj.getParticipants().clear(), or rather even RelationParticipant.setRelationObject(null). Or are you basically saying em.remove(relobj) also counts as disconnecting its participants? – Maksim Gumerov Dec 17 '15 at 04:19
  • Wait, I've found it myself: "If the remove operation is applied to a managed source entity, the remove operation will be cascaded to the relationship target in accordance with the rules of section 3.2.3, (and hence it is not necessary to specify cascade=REMOVE for the relationship)" – Maksim Gumerov Dec 17 '15 at 04:24
  • The collections should be OK before removal. I've just fetched relobj with a query, so its links to participants (and reverse linkes) are maintained by JPA provider. I checked that just now, there are 2 links to different participant, each of participants has a link to relobj (that same instance). Calling refresh changed nothing, still getting that exception when orphanRemoval = true, unless at least one participant is deleted beforehand. – Maksim Gumerov Dec 17 '15 at 05:13
  • Turn on SQL logging and see what changes in the statement ordering, and what exactly is causing the constraint violation. – Chris Dec 21 '15 at 14:32
0

After a couple of days' research I found out what looks like the following behavior. OrphanRemoval only cascades removal to entities which becomes orphans; and not just orphans by one relation (whose source is being removed), but orphans by all relations. Expample: if I delete Source which contains X in its Target[], X will become orphan UNLESS there is another source S for which X is also a Target. Of course this is natural when X is "master" of S (S contains FK to X); but the cascading will not happen even if X is "child" of S (X contains FK to S, and X is source for inverse relation: "mappedBy=..."). I can't find this special condition in JPA specification, so maybe I am wrong? But at least this explanation makes sense.

So, removing relobj will NOT cascade to its RelationParticipants in my example because every one of the latter is also referred by its BusinessObject (as an inverse, non-owning side of relation).

Another interesting bit of behavior: cascading CAN STILL occur even in that circumstances, if EntityManager does not know about any obstacles. Say, in DB there is a BusinessObject which refers to RelationParticipant, but it was not yet loaded by EntityManager. In this case EntityManager will not detect this BusinessObject's inverse reference to RelationParticipant, and em.remove(relobj) will successfully cascade to relobj.getParticipants().

Maksim Gumerov
  • 642
  • 8
  • 23
  • http://stackoverflow.com/a/34843369/4101144 could be describing similar situation and gives an exhaustive answer. Among other things it explains how Hibernate traverses all references when flushing DB, so if a reference contains MERGE-cascading, it can cancel deletion which was scheduled by orphan-removal via another reference. I should note, though, that in my case I did not get any "un-scheduling" messages in the log! – Maksim Gumerov Jan 19 '16 at 06:38