Bidirectional relationships in JPA

4k views Asked by At

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.

  • When an Employee entity is persisted, it is listed by the list on the inverse side (I am not explicitly adding the newly persisted Employee entity to the List<Employee> on the inverse side).
  • When an Employee entity is merged, changes made to the entity are automatically reflected by the corresponding entity in the List<Employee> on the inverse side (I am not explicitly changing the corresponding entity held by the list of employees (List<Employee>) on the inverse side).
  • Similarly, when an Employee entity is removed, it is also removed from the list on the inverse side of the relationship (I am not explicitly removing that entity from the list on the inverse side).

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

1

There are 1 answers

3
codedabbler On BEST ANSWER

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).