Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA: question on impedance mismatch in OneToMany relations

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?

like image 233
wallenborn Avatar asked Dec 10 '22 14:12

wallenborn


1 Answers

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.

like image 177
Pascal Thivent Avatar answered Dec 12 '22 03:12

Pascal Thivent