CascadeType is misbehaving!
justinmiller Mar 28, 2007 11:30 PMI am seeing something very weird with respect to cascading. There are really two problems. Let's say that I have classes A and B, and then a main method below that:
@Entity @Table(name="A") public class A { @Column(name="ID") private String id; @OneToMany(cascade= {CascadeType.REMOVE, CascadeType.REFRESH, CascadeType.PERSIST}) @JoinColumn(name="PARENT_ID") private Collection<B> children; public A() { super(); } public A(String id) { this.id = id; } public Collection<B> getChildren() { return children; } public void setChildren(Collection<B> children) { this.children = children; } public String getId() { return id; } public void setId(String id) { this.id = id; } }
@Entity @Table(name="B") public class B implements Serializable { @Column(name="ID") private String id; @Column(name="PARENT_ID") private String parent; public B() { super(); } public B(String id) { this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getParent() { return parent; } public void setParent(String parent) { this.parent = parent; } }
public class EJB3Test { public static void main(String[] args) { A a = new A(generateId()); B b = new B(generateId()); a.setChildren(Arrays.asLis(new B[] { b })); b.setParent(a.getId()); //for argument's sake, let's just say SessionFacade is just a wrapper //around EntityManager.persist/merge/remove SessionFacade.persist(a); a = new A(a.getId()); //let's say we've changed 'a' somehow, and we want those changes //persisted, but we WANT it's relationship to it's children //preserved. SessionFacade.merge(a); } }
The first problem:
Notice how I don't have CascadeType.MERGE set on the relationship between A and it's children. Why then, when I do the merge(), will it attempt to set B.PARENT_ID to null? I would think that if it's not set to cascade on a merge, the EntityManager shouldn't even touch children. I have found however, that setting updatable=false on the JoinColumn causes the expected behavior.
The second problem is an extension of the first:
Let's take it one step further, assuming we set updatable=false, and right before we try SessionFacade.merge(a), we do this:
a.setChildren(Arrays.asList(new B[] { new B(someNewId) } )); //then do: SessionFacade.merge(a)
The merge now throws an exception, even though it's NOT set to cascade on merge, and children is set to updatable=false. The exception we get is an EntityNotFoundException stating that it can't find an object B with id: someNewId. Well no kidding! It's not there! I know it seems weird to NOT want to cascade, but I assure you - in my context, I simply need more control over how the children are inserted. Therefore, it makes perfect sense to have children that I do not want persisted until I explicitly do so.
I shouldn't have to grab a reference to the children, null out the field in A, and then merge all the children.
Collection<B> children = a.getChildren(); a.setChildren(null); SessionFacade.merge(a); for(B child : children) SessionFacade.merge(child);
ANY help would be greatly appreciated!!! This is driving me crazy!