Search code examples
javahibernatejpahibernate-mappingbidirectional-relation

Adding a newly persisted entity to a list of entities held by the inverse side of a relationship so as to maintaining a bidirectional relationship


Given two entities Department and Employee forming a one-to-many relationship from Department to Employee.

Since the relationship is quite intuitive, I am leaving out the entity classes.

The following segment of code, simply persists an entity Employee.

public void insert() {
    Employee employee = new Employee();
    employee.setEmployeeName("k");

    Department department = entityManager.find(Department.class, 1L);
    employee.setDepartment(department);
    entityManager.persist(employee);
    entityManager.flush();

    List<Employee> employeeList = department.getEmployeeList();
    employeeList.add(employee);
}

And the following method returns a list of employees associated with a particular department.

public List<Employee> getList() {
    return entityManager.find(Department.class, 1L).getEmployeeList();
}

Both the methods are written in a stateless EJB using CMT (hereby not BMT) named let's say EmployeeService.

A client application invokes these methods in sequence like so,

employeeService.insert();
List<Employee> employeeList = employeeService.getList();

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

The sout statement in the foreach loop above displays a newly added Employee entity to List<Employee> in Department with a null employeeId in it given that the line entityManager.flush(); is not present in the very first code snippet.


EntityManager#persist(Object entity) is not guaranteed to generate an id. An id is only guaranteed to be generated at flush time.

What happens is, if entityManager.flush(); is removed/commented, then the entity Employee is added to the list of Employees (List<Employee> employeeList) with a null identifier in it (the primary key column in the underlying database table).

What is the usual way to maintain a bidirectional relationship? Is EntityManager#flush(); always needed every time an entity is to be added to a collection of entities being maintained by the inverse side of the relationship to generate an id associated with a newly persisted entity?

Also, is it always required to manually delete an Employee from List<Employee> (maintained by the inverse side of the relationship - Department) while deleting an Employee entity (using entityManager.remove(employee);)?


EDIT : Entity classes :

Department :

@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().
}

Employee :

@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().
}

Solution

  • When persisting the Employee, you need to set both sides of the association.

    In your Department you should have this method:

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

    Make sure you cascade de PERSIST and MERGE events to your children association:

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "department", orphanRemoval = true)
    private List<Employee> children = new ArrayList<>();
    

    and the persisting logic becomes:

    Employee employee = new Employee();
    employee.setEmployeeName("k");
    
    Department department = entityManager.find(Department.class, 1L);
    department.addEmployee(employee);