views:

347

answers:

2

I have the following annotated Hibernate entity classes:

@Entity
public class Cat {
    @Column(name = "ID") @GeneratedValue(strategy = GenerationType.AUTO) @Id
    private Long id;

    @OneToMany(mappedBy = "cat", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Kitten> kittens = new HashSet<Kitten>();

    public void setId(Long id) { this.id = id; }
    public Long getId() { return id; }
    public void setKittens(Set<Kitten> kittens) { this.kittens = kittens; }
    public Set<Kitten> getKittens() { return kittens; }
}

@Entity
public class Kitten {
    @Column(name = "ID") @GeneratedValue(strategy = GenerationType.AUTO) @Id
    private Long id;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Cat cat;

    public void setId(Long id) { this.id = id; }
    public Long getId() { return id; }
    public void setCat(Cat cat) { this.cat = cat; }
    public Cat getCat() { return cat; }
}

My intention here is a bidirectional one-to-many/many-to-one relationship between Cat and Kitten, with Kitten being the "owning side".

What I want to happen is when I create a new Cat, followed by a new Kitten referencing the Cat, the Set of kittens on my Cat should contain the new Kitten. However, this does not happen in the following test:

@Test
public void testAssociations()
{
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Transaction tx = session.beginTransaction();

    Cat cat = new Cat();
    session.save(cat);

    Kitten kitten = new Kitten();
    kitten.setCat(cat);
    session.save(kitten);

    tx.commit();

    assertNotNull(kitten.getCat());
    assertEquals(cat.getId(), kitten.getCat().getId());
    assertTrue(cat.getKittens().size() == 1); // <-- ASSERTION FAILS
    assertEquals(kitten, new ArrayList<Kitten>(cat.getKittens()).get(0));
}

Even after re-querying the Cat, the Set is still empty:

// added before tx.commit() and assertions
cat = (Cat)session.get(Cat.class, cat.getId());

Am I expecting too much from Hibernate here? Or is the burden on me to manage the Collection myself? The (Annotations) documentation doesn't make any indication that I need to create convenience addTo*/removeFrom* methods on my parent object.

Can someone please enlighten me on what my expectations should be from Hibernate with this relationship? Or if nothing else, point me to the correct Hibernate documentation that tells me what I should be expecting to happen here.

What do I need to do to make the parent Collection automatically contain the child Entity?

+5  A: 

It won't automatically add it. You have to add it yourself.

I wouldn't directly call Kitten.setCat() either. The typical pattern for this is to put a method in Cat like:

public void addKitten(Kitten kitten) {
  if (kittens == null) {
    kittens = new HashSet<Kitten>();
  }
  kittens.add(kitten);
  kitten.setCat(this);
}

and then simply call:

cat.addKitten(kitten);
cletus
Is there somewhere in the Hibernate documentation that would have told me this? I feel like (at least the Annotations docs) are lacking in this regard.
Rob Hruska
cletus
@Cletus - Thanks for the answer and the book recommendation.
Rob Hruska
+2  A: 

When working with bi-directional associations, you have to handle both sides of the "link" and it is very common to use defensive link management methods for that, as suggested by @cletus. From Hibernate Core documentation:

1.2.6. Working bi-directional links

First, keep in mind that Hibernate does not affect normal Java semantics. How did we create a link between a Person and an Event in the unidirectional example? You add an instance of Event to the collection of event references, of an instance of Person. If you want to make this link bi-directional, you have to do the same on the other side by adding a Person reference to the collection in an Event. This process of "setting the link on both sides" is absolutely necessary with bi-directional links.

Many developers program defensively and create link management methods to correctly set both sides (for example, in Person):

protected Set getEvents() {
    return events;
}

protected void setEvents(Set events) {
    this.events = events;
}

public void addToEvent(Event event) {
    this.getEvents().add(event);
    event.getParticipants().add(this);
}

public void removeFromEvent(Event event) {
    this.getEvents().remove(event);
    event.getParticipants().remove(this);
}

The get and set methods for the collection are now protected. This allows classes in the same package and subclasses to still access the methods, but prevents everybody else from altering the collections directly. Repeat the steps for the collection on the other side.

More References

Pascal Thivent
@Pascal - Thanks for the links; the Hibernate documentation covers quite a bit, it just seems like it's hard to find exactly what you're looking for when you're wondering about a specific thing (like this). The first link you provided pretty much sums up what I was interested in: "This process of 'setting the link on both sides' is absolutely necessary with bi-directional links."
Rob Hruska
@Rob I agree and indeed, the tutorial part is the more explicit about this. Just in case, I consider Bauer's book [Java Persistence with Hibernate](http://www.manning.com/bauer2/) as a must have companion when working with Hibernate (876 pages!).
Pascal Thivent