Saturday 3 December 2011

JPA one-to-many many-to-one relationship

many-to-one unidirectional mapping

@Entity
@Table(name="T_MANAGER")
public class Manager {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="MANA_ID")
    private int id;
    
    @Column(name="MANA_NAME")
    private String name;
}

@Entity
@Table(name="T_EMPLOYEE")
public class Employee {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="EMPL_ID")
    private int id;
    
    @Column(name="EMPL_NAME")
    private String name;

    @ManyToOne(cascade=CascadeType.PERSIST)
    private Manager manager;
}

Domain model


T_EMPLOYEE.MANAGER_MANA_ID is the foreign key referenced to T_MANAGER.MANA_ID

MANAGER_MANA_ID is the default foreign key column name. It is derived from class name + ‘_’ + primary key name (‘MANAGER’ + ‘_’+’MANA_ID’)

Test:

@PersistenceContext(unitName="unit")
private EntityManager em;

private void save(){
    Manager manager1 = new Manager("John");
    Employee employee1 = new Employee("Bob");
    Employee employee2 = new Employee("Tim");
    Employee employee3 = new Employee("Mary");
    employee1.setManager(manager1);
    employee2.setManager(manager1);
    employee3.setManager(manager1);
    
    Manager manager2 = new Manager("Robert");
    Employee employee4 = new Employee("Tom");
    Employee employee5 = new Employee("Alex");
    employee4.setManager(manager2);
    employee5.setManager(manager2);
    em.persist(employee1);
    em.persist(employee2);
    em.persist(employee3);
    em.persist(employee4);
    em.persist(employee5);
}
Result:

T_MANAGER


T_EMPLOYEE


Specify foreign key column name

@OneToOne(cascade={CascadeType.PERSIST, 
    CascadeType.MERGE, CascadeType.REMOVE}, 
    optional=false, fetch=FetchType.EAGER)
@JoinColumn(name="F_ADDR_ID", referencedColumnName="ADDR_ID")
private Address address;

Domain model


The name attribute of @JoinColumn (‘F_MANA_ID’) specifies the foreign key column name.

many-to-one bidirectional mapping

@Entity
@Table(name="T_MANAGER")
public class Manager {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="MANA_ID")
    private int id;
    
    @Column(name="MANA_NAME")
    private String name;
    
    @OneToMany(cascade={CascadeType.ALL}, mappedBy="manager")
    private Set<Employee> employees;
}

@Entity
@Table(name="T_EMPLOYEE")
public class Employee {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="EMPL_ID")
    private int id;
    
    @Column(name="EMPL_NAME")
    private String name;

    @ManyToOne(cascade=CascadeType.PERSIST)
    @JoinColumn(name="F_MANA_ID")
    private Manager manager;
}

Domain model

Same as unidirectional mapping


Test:

@PersistenceContext(unitName="unit")
private EntityManager em;

private void save(){
    Manager manager1 = new Manager("John");
    Employee employee1 = new Employee("Bob");
    Employee employee2 = new Employee("Tim");
    Employee employee3 = new Employee("Mary");
    Set<Employee> employees1 = new HashSet<Employee>();
    employee1.setManager(manager1);
    employee2.setManager(manager1);
    employee3.setManager(manager1);
    employees1.add(employee1);
    employees1.add(employee2);
    employees1.add(employee3);
    manager1.setEmployees(employees1);
    
    Manager manager2 = new Manager("Robert");
    Employee employee4 = new Employee("Tom");
    Employee employee5 = new Employee("Alex");
    Set<Employee> employees2 = new HashSet<Employee>();
    employees2.add(employee4);
    employees2.add(employee5);
    employee4.setManager(manager2);
    employee5.setManager(manager2);
    manager2.setEmployees(employees2);
    
    em.persist(manager1);
    em.persist(manager2);
}

Result:

Same as unidirectional mapping

T_MANAGER


T_EMPLOYEE


Retrieving the employees of a manager returns null.

Manager manager = em.find(Manager.class, 1);
System.out.println(manager.getEmployees());

This is because in a one-to-many relationship, the default fetch type at ONE side is LAZY.

@OneToMany(cascade={CascadeType.ALL}, mappedBy="manager", 
           fetch=FetchType.EAGER)
private Set<Employee> employees;

Change the fetch type to EAGER, and we are able to retrieve the employees of a manager.

Add a new employee to a manager

Manager manager = em.find(Manager.class, 1);
Employee emp = new Employee("Adam");
emp.setManager(manager);
manager.getEmployees().add(emp);
em.merge(manager);

Result:


Update an existing employee of a manager

Manager manager = em.find(Manager.class, 1);
Employee employee = manager.getEmployees().iterator().next();
employee.setName("Josh");
em.merge(manager);

Result:



Delete an existing employee of a manager

Manager manager = em.find(Manager.class, 1);
Set<Employee> employees= manager.getEmployees();
for (Employee emp: employees){
    if ("Bob".equalsIgnoreCase(emp.getName())){
        employees.remove(emp);
        break;
    }
}
em.merge(manager);

Result:


Bob is not deleted! We can see that simply removing the object from the set won’t actually remove the row from the database. So in this case, we need to invoke em.remove() to delete the row.

Manager manager = em.find(Manager.class, 1);
Set<Employee> employees= manager.getEmployees();
for (Employee emp: employees){
    if ("Bob".equalsIgnoreCase(emp.getName())){
        employees.remove(emp);
        em.remove(emp);
        break;
    }
}
em.merge(manager);

Result: