hibernatejpaeclipselinkjpa-2.1bidirectional-relation

Bidirectional relationships in JPA


I am missing something very fundamental. Given below two entities Department (inverse side) and Employee (owning side) forming a one-to-many relationship from Department to Employee.

Department.java

@Entity
@Table(catalog = "testdb", schema = "", uniqueConstraints = {
    @UniqueConstraint(columnNames = {"department_id"})})
public class Department implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "department_id", nullable = false)
    private Long departmentId;

    @Column(name = "department_name", length = 255)
    private String departmentName;

    @Column(length = 255)
    private String location;

    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
    private List<Employee> employeeList = new ArrayList<Employee>(0);

    private static final long serialVersionUID = 1L;

    // Constructors + getters + setters + hashcode() + equals() + toString().
    // No defensive link (relationship) management methods have yet been added to.
    // CascadeType is also kept at a distance for now.
}

Employee.java

@Entity
@Table(catalog = "testdb", schema = "", uniqueConstraints = {
    @UniqueConstraint(columnNames = {"employee_id"})})
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "employee_id", nullable = false)
    private Long employeeId;

    @Column(name = "employee_name", length = 255)
    private String employeeName;

    @JoinColumn(name = "department_id", referencedColumnName = "department_id")
    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;

    private static final long serialVersionUID = 1L;

    // Constructors + getters + setters + hashcode() + equals() + toString().
    // No defensive link (relationship) management methods have yet been added to.
    // CascadeType is also kept at a distance for now.
}

Given below some methods in a stateless EJB (using CMT) doing persist, merge and remove operations respectively.

public List<Employee> persist() {
    Employee employee = new Employee();
    employee.setEmployeeName("a");
    employee.setDepartment(entityManager.getReference(Department.class, 1L));
    entityManager.persist(employee);
    return employee.getDepartment().getEmployeeList();
}

public List<Employee> merge(Employee employee) {
    employee.setEmployeeName("b");
    employee.setDepartment(entityManager.getReference(Department.class, 1L));
    return entityManager.merge(employee).getDepartment().getEmployeeList();
}

public List<Employee> remove(Employee employee) {
    entityManager.remove(entityManager.contains(employee) ? employee : entityManager.merge(employee));
    return entityManager.getReference(Employee.class, employee.getEmployeeId()).getDepartment().getEmployeeList();
}

public Employee getEmployeeById(Long id) {
    return entityManager.find(Employee.class, id);
}

These methods are invoked by the associated application client in turn (one by one) in a non-transactional environment.

List<Employee> persistedList = employeeSessionBean.persist();
for (Employee employee : persistedList) {
    System.out.println(employee.getEmployeeId() + " : " + employee.getEmployeeName());
}

List<Employee> mergedList = employeeSessionBean.merge(employeeSessionBean.getEmployeeById(23L));
for (Employee employee : mergedList) {
    System.out.println(employee.getEmployeeId() + " : " + employee.getEmployeeName());
}

List<Employee> listAfterRemoving = employeeSessionBean.remove(employeeSessionBean.getEmployeeById(23L));

for (Employee employee : listAfterRemoving) {
    System.out.println(employee.getEmployeeId() + " : " + employee.getEmployeeName());
}

The list (List<Employee>) on the inverse side of the relationship automatically reflects the correct state during every operation as above.

I am currently on EcliseLink 2.6.0. Why do I see such behaviour which does not appear to match the following text?

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.

http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#Bi-directional_Many_to_Many


Solution

  • What this means is that with your particular example, if you change the code to add the employee to the department (and not the other way of setting the department) then you will notice that this will not automatically set the department on the employee. You will have to write code to explicit do this.

    So, even though the particular code path as you have shown does work, it does not mean that you can rely on it. I can take a guess why this works - the collection is lazily loaded, and since the object is persisted before the collection is loaded, it is able to pull the correct data from the DB.

    The best solution is to heed the advice in the documentation and set the state correctly on both ends of the bi-directional relationship, performance considerations notwithstanding (this can be fine-tuned later).