1

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"

user973479
  • 1,599
  • 5
  • 25
  • 47

2 Answers2

3

It seems to be doing exactly what you told it to:

ObjectOne obj1 = findInDatabase();

Load a persistent object.

obj1.addSet(objTwoSet);

Add the content of a set to the object's existing set (thus "merging" the two sets).

sessionFactory.getCurrentSession().saveOrUpdate(obj1);

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:

obj1.clearSet();
obj1.addSet(objTwoSet);

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:

obj1.addSet(objTwoSet);

you need something like:

for (ObjectTwo o : obj1.getSet()) {
    o.setObjectOne(null);
}
obj1.addSet(objTwoSet);

This is most likely your problem. It's never okay to leave your object model in a broken state.

Ryan Stewart
  • 115,853
  • 19
  • 167
  • 192
  • Also, if I had to make a call to a clearSet method and then make a call to a different method to add a new set I would have to pull back the existing set and manually compare them to see if there's differences. In other words, I wouldn't want to clear then re-add the same set. I'd assume hibernate has some way of doing all this for you automatically? – user973479 Dec 22 '11 at 18:23
  • I'm attempting to do the latter. Thanks for the advice, makes sense! I'll try it out shortly and see if that fixes it. – user973479 Dec 22 '11 at 18:43
  • Turns out it didn't work. I can't set the other Objects property to null because it is a required field and throws constraint errors from the database. – user973479 Dec 23 '11 at 12:12
  • Setting the attribute for orphanRemoval doesn't work either. I am also getting a UniqueConstraint exception from the database (An entry already exists) for the Set. – user973479 Dec 23 '11 at 12:24
  • If the relationship can't be null, then you either have to give it a different "owner" or else delete it. A unique constraint violation means just what it sounds like: you have some kind of unique constraint, and you're trying to add an object that violates the constraint. That's not really related to your original question. – Ryan Stewart Dec 23 '11 at 16:02
1

Try the following implementation:

public class ObjectOne{
  @OneToMany(cascade = CascadeType.ALL, orphanRemoval=true)
  Set<ObjectTwo> obj2;

  public void addSet(Set<ObjectTwo> newSet){
    obj2.clear();
    obj2.addAll(newSet);
  }
}

Is the association bi-directional, i.e. does ObjectTwo also reference ObjectOne? If so, you must also set this reference to null before you clear the obj2 set.

public class ObjectOne{
  @OneToMany(cascade = CascadeType.ALL, orphanRemoval=true)
  Set<ObjectTwo> obj2;

  public void addSet(Set<ObjectTwo> newSet){
    for (ObjectTwo two : obj2) {
      two.setObjectOne(null);
    }
    obj2.clear();
    for (ObjectTwo two : newSet) {
      two.setObjectOne(this);
      obj2.add(two);
    }
  }
}
tscho
  • 1,926
  • 12
  • 15
  • Tscho, I apologize but I don't think my original question was written well. Please see the updated version and my situation may make more sense. – user973479 Dec 23 '11 at 13:57