Update: I rewrote the question to make as much sense as possible and provide the most information.
I have the following:
@Entity
public class FooLnkBar implements java.io.Serializable {
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "SomeID", nullable = false)
private Foo foo;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "OtherID", nullable = false)
private Bar bar;
}
@Entity
public class Foo implements java.io.Serializable {
@OneToMany(orphanRemoval=true, cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "foo")
private Set<FooLnkBar> fooLnkBars = new HashSet<FooLnkBar>(0);
}
Case 1: Save everything by persisting FooLnkBar
// This correctly saves everything into the database in one shot
Foo foo = new Foo();
Bar bar = new Bar();
FooLnkBar flb = new FooLnkBar();
flb.setFoo(foo);
flb.setBar(bar);
persist(flb);
Case 2: Modify a FooLnkBar and persist a Foo
// Load a Foo from the database and get the set of FooLnkBars
Foo foo = loadFooFromDatabase();
Set<FooLnkBar> fooLnkBars = foo.getFooLnkBars();
// Delete all existing FooLnkBar objects. Alternatively use removeAll()
Iterator<FooLnkBar> it = fooLnkBars.iterator();
while (it.hasNext()) {
FooLnkBar o = it.next();
it.remove();
}
// Add some new FooLnkBars
Foo foo = new Foo();
Bar bar = new Bar();
FooLnkBar flb = new FooLnkBar();
fooLnkBars.add(flb);
foo.setFooLnkBars(fooLnkBars);
// Save all changes
persist(foo);
Case 2 does not work. Because of the cascadeType, this also removes the Foo in which the FooLnkBar resides, so the entire Foo object in Case 2 is deleted. This is bad.
Furthermore, I want to ensure that hibernate doesn’t see Foo as a duplicate row when I try to persist it since FooLnkBar is a value in another table.
If I delete a Foo from the database the FooLnkBar should also be deleted from the database.
If I delete a FooLnkBar from the database the Foo should NOT be deleted from the database.
I am looking for a way to get all cases working.
Update: Changing this fixes Case 2 but breaks Case 1:
@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinColumn(name = "SomeID", nullable = false)
private Foo foo;
The Error message in Case 1 is now “not-null property references a null or transient value: FooLnkBar.foo”
It seems to be doing exactly what you told it to:
Load a persistent object.
Add the content of a set to the object’s existing set (thus “merging” the two sets).
Unnecessarily saveOrUpdate() the object. The object is already attached to the session, and therefore its state will properly be persisted when the enclosing transaction commits. You can do away with this line.
In order to clear the existing content of the set and add all new content, you should do exactly that:
Update: A Set isn’t treated as a single, discrete object in Hibernate. It’s just a grouping of entities, so when you say “remove the first Set from the database”, you’re actually saying one of “remove all the entities contained in the first set from the database” or “disassociate all the entities contained in the first set from the given object in the database”.
For the former, you’d use the orphanRemoval attribute of @OneToMany, and see the prototypical parent/child relationships demonstrated by the reference guide.
For the latter, you just do exactly what you’re doing, but if it’s a bi-directional relationship, you must also correctly set the other end. So instead of a simple:
you need something like:
This is most likely your problem. It’s never okay to leave your object model in a broken state.