views:

44

answers:

3

I have a question about JPA-2.0 (provider is Hibernate) relationships and their corresponding management in Java. Let's assume i have a Department and an Employee entity:

@Entity
public class Department {
  ...
  @OneToMany(mappedBy = "department")
  private Set<Employee> employees = new HashSet<Employee>();
  ...
}

@Entity
public class Employee {
  ...
  @ManyToOne(targetEntity = Department.class)
  @JoinColumn
  private Department department;
  ...
}

Now i know i have to manage the Java relationships myself, as in the following unit test:

@Transactional
@Test
public void testBoth() {
  Department d = new Department();
  Employee e = new Employee();
  e.setDepartment(d);
  d.getEmployees().add(e);
  em.persist(d);
  em.persist(e);
  assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
  assertNotNull(em.find(Department.class, d.getId()).getEmployees());
}

If i leave out either e.setDepartment(d) or d.getEmployees().add(e) the assertions will fail. So far, so good. What if i commit the database transaction in between?

@Test
public void testBoth() {
  EntityManager em = emf.createEntityManager();
  em.getTransaction().begin();
  Department d = new Department();
  Employee e = new Employee();
  e.setDepartment(d);
  d.getEmployees().add(e);
  em.persist(d);
  em.persist(e);
  em.getTransaction().commit();
  em.close();
  em = emf.createEntityManager();
  em.getTransaction().begin();
  assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
  assertNotNull(em.find(Department.class, d.getId()).getEmployees());
  em.getTransaction().commit();
  em.close();
}

Do i still need to manage both sides of the relation? No, as it turns out, i don't have to. With this modification

e.setDepartment(d);
//d.getEmployees().add(e);

the assertions still succeed. However, if i only set the other side:

//e.setDepartment(d);
d.getEmployees().add(e);

the assertions fail. Why? Is is because the Employee is the owning side of the relation? Can i change that behavior by annotating differently? Or is it just always the "One" side of the "OneToMany" that determines when the foreign key field in the database is filled?

+2  A: 

I don't know what your test is trying to demonstrate but the fact is you must handle both sides of the association when working with bidirectional associations. Not doing so is incorrect. Period.

Update: While the spec reference mentioned by axtavt is of course accurate, I insist, you definitely must set both sides of a bi-directional association. Not doing so is incorrect and the association between your entities in the first persistence context is broken. The JPA wiki book puts it like this:

As with all bi-directional relationships it is your object model's and application's responsibility to maintain the relationship in both direction. There is no magic in JPA, if you add or remove to one side of the collection, you must also add or remove from the other side, see object corruption. Technically the database will be updated correctly if you only add/remove from the owning side of the relationship, but then your object model will be out of synch, which can cause issues.

In other words, the only correct and safe way to manage your bidirectional association in Java is to set both sides of the link. This is usually done using defensive link management methods, like this:

@Entity
public class Department {
    ...
    @OneToMany(mappedBy = "department")
    private Set<Employee> employees = new HashSet<Employee>();
    ...

    public void addToEmployees(Employee employee) {
        this.employees.add(employee);
        employee.setDepartment(this);
    }
}

I repeat, not doing so is incorrect. Your test only works because you're hitting the database in a new persistence context (i.e. a very particular situation, not the general one) but the code would break in many other situations.

Pascal Thivent
I wasn't suggesting a shortcut to proper relation management, i was just a little puzzled by the observed behavior. So i'll make sure the Java side always manages both sides of the relationship, thanks.
wallenborn
@wallenborn Sorry, I somehow misunderstood the question then.
Pascal Thivent
+2  A: 

Entity relationships in JPA have owning and inverse sides. Database updates are determined by the state of the owning side. In your case Employee is an owning side due to the mappedBy attribute.

From the JPA 2.0 specification:

2.9 Entity Relationships

...

Relationships may be bidirectional or unidirectional. A bidirectional relationship has both an owning side and an inverse (non-owning) side. A unidirectional relationship has only an owning side. The owning side of a relationship determines the updates to the relationship in the database, as described in section 3.2.4.

The following rules apply to bidirectional relationships:

  • The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy element of the OneToOne, OneToMany, or ManyToMany annotation. The mappedBy element designates the property or field in the entity that is the owner of the relationship.
  • The many side of one-to-many / many-to-one bidirectional relationships must be the owning side, hence the mappedBy element cannot be specified on the ManyToOne annotation.
  • For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.
  • For many-to-many bidirectional relationships either side may be the owning side.
axtavt
Thank you, that answers my question.
wallenborn
+1  A: 

The reason why the second test in a new persistence context succeeds if you only update the owning side in a previous context is that the persistence provider obviously can't know that when persisting you did not update the inverse side as well. It only cares about the owning side for persistence purposes. However, when you get persistent objects from a persistence provider, the provider sets the bidirectional associations properly on both sides (it is simply assumed they were persisted properly, too). However, as many others here have already pointed out, it is not the responsibility of the persistence provider to complete newly created bidirectional associations and you should always properly maintain bidirectional associations in your code.

Yeah, the database can only store one piece of information, and when the object is retrieved from the database JPA reconstructs both sides.
wallenborn