Hibernate Mappings
In database, the data in one table can be linked to the data in another table through foreign key relations or simple id mappings. If we want to achieve this in hibernate, we have to create the appropriate relation between the classes.
Scenario
- Employee works for an organization
- At the workplace, employee is provided with a desktop and a telephone.
- Employee has also registered his e-mail to several groups.
This is a typical employee-organization scenario. Lets try to design this scenario using JPA Entities and associations.
Database design
To design this, we will have to create 3 different tables namely, Employee, EmployeeDetails, Device and RegisteredGroup and define relationships among the tables.One to One
- Details of an Employee - Personal, Professional, etc.,(We can have multiple tables for personal and professional. But, we will be using 1 table here)
- Employee(1) - EmployeeDetails(1)
One to Many
- An Employee can have 0 or more devices(s) assigned to him
- Employee(1) - Device (o..*)
Many to Many
- An Employee is registered to 0 or many groups and vice versa
- Employee(0..*) - RegisteredGroup (0..*)
Hibernate/JPA Implementation
Employee
Here Employee class is a root class. Because, we are maintaining the employee data in 3 different tables by defining the relations between each table with employee table.
For defining the relations, In Employee class we will be creating fields for EmployeeDetails, devices and groups and annotate them with the respective relation.
For defining the relations, In Employee class we will be creating fields for EmployeeDetails, devices and groups and annotate them with the respective relation.
@Entity public class Employee { @Id @GeneratedValue(strategy=GenerationType.AUTO) private int employeeId; @Column(name="EMPLOYEE_NAME") private String employeeName; @OneToOne(mappedBy="employee", cascade=CascadeType.ALL)
private EmployeeDetails details; @OneToMany(mappedBy="employee", cascade=CascadeType.ALL) private Collection<Device> devices = new HashSet<>(); @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}) @JoinTable(name="EMPLOYEE_GROUPS", joinColumns=@JoinColumn(name="EMP_ID"),
inverseJoinColumns=@JoinColumn(name="GROUP_ID")) private Collection<RegisteredGroup> groups = new HashSet<>(); //Getters and setters ... }
One to One Mapping
The below snippet in the Employee class enables one to one mapping between Employee and Department class.The significance of each annotation is explained below.
@OneToOne(mappedBy="employee", cascade=CascadeType.ALL)
private EmployeeDetails details;
@OneToOne: Defines One to One relation b/w Employee class and EmployeeDetails class. It means the Employee class objects can access the associated class objects using getters and setters.
CascadeType.ALL: By declaring cascade, Persisting/Merging/Deleting the employee object allows associated objects to be persisted/merged/deleted automatically.
employee.setDetails(employeeDetails);
session.persist(employee); saves both the employee and details objects.
session.persist(
mappedBy="employee": Hints the hibernate that the foreign key column has already been declared in EmployeeDetails class employee variable (explained below.) and it will be created when hibernate loads the EmployeeDetails class. So, by defining mappedBy in Employee class, hibernate will not be creating the mapping column again while loading the Employee class. This avoids duplicate foreign/mapping key columns.
EmployeeDetails Class
@Entity public class EmployeeDetails { @Id @GeneratedValue(strategy=GenerationType.AUTO) private int id; @Column(name="JOINED_DATE") private Date joinedDate; @Column(name="EXPERIENCE") private int experience; @OneToOne @JoinColumn(name="EMP_ID") private Employee employee; //getters and setters ... }
To make the one to one mapping between EmployeeDetails and Employee classes bi-directional, @OneToOne annotation has to be declared again in this class. It means this class objects can also access the associated employee class object.
We can define the cascade in Department class as well. But, it is always a good practice to let parent class Employee cascades the child class entities.
We can define the cascade in Department class as well. But, it is always a good practice to let parent class Employee cascades the child class entities.
@JoinColumn(name="EMP_ID") : Creates a foreign key/mapping column EMP_ID in the Department table which maps employee and EmployeeDetails tables.
One to Many Mapping
The below snippet in the Employee class enables one to many mapping between Employee and Device class.The significance of each annotation is explained below.@OneToMany(mappedBy="employee", cascade=CascadeType.ALL)
private Collection<Device> devices = new HashSet<>();
@OneToMany: creates one to many mapping between Employee and Device classes.
cascade=CascadeType.ALL: By declaring cascade, Persisting/Merging/Deleting the employee object allows associated device objects to be persisted/merged/deleted automatically. In one to many mapping, we can only define the "cascade" at the parent class level which is Employee class. Parent should only cascade the child entities not the vice versa. This is explained below while explaining the @JoinColumn annotation.
employee.setDevice(device1);
employee.setDevice(device2);
session.persist(employee); saves both the employee and device objects.
session.persist(device1);
session.persist(device2); is not required. Can be eliminated.
Device Class
@Entity public class Device { @Id @GeneratedValue(strategy=GenerationType.AUTO) private int deviceId; @Column(name="DEVICE_NO") private int deviceNo; @Column(name="DEVICE_NAME") private String deviceName; @ManyToOne @JoinColumn(name="EMPLOYEE_ID") private Employee employee; //getters and setters ... }
@ManyToOne
@JoinColumn(name="EMPLOYEE_ID")
private Employee employee
@ManyToOne: To make the one to many mapping between Device and Employee classes bi-directional, @ManyToOne annotation has to be declared again in the Device class. It means the Device class objects can also access the associated employee class object from the Device class.
@JoinColumn(name="EMPLOYEE_ID") : Creates a foreign key/mapping column EMPLOYEE_ID in the Device table which maps employee and device tables. We can't declare the @JoinColumn(name="DEVICE_ID") annotation in Employee class as we did in one to one mapping. Because, we cannot map a single employee row to many device rows by holding a foreign key of DEVICE_ID in employee table. So, below is the only possible schema solution.
Many to Many Mapping
The below snippet in the Employee class enables many to many mapping between Employee and RegisteredGroup class.The significance of each annotation is explained below.
@JoinTable(name="EMPLOYEE_GROUPS",
joinColumns=@JoinColumn(name="EMP_ID"),
inverseJoinColumns=@JoinColumn(name="GROUP_ID"))
private Collection<RegisteredGroup> groups = new HashSet<>();
@ManyToMany: creates many to many mapping between Employee and Registered group classes.
cascade={CascadeType.PERSIST, CascadeType.MERGE}: By declaring cascade, Persisting/Merging the employee object allows associated RegisteredGroup objects to be persisted/merged automatically. In many to many mapping, we should be cautious while using cascadeType.REMOVE. Because, remove can delete some unexpected records when the cascadeType is declared on the other side which is at RegisteredGroup Class as well.
Please observe the highlighted yellow lines in the code snippet below:
Here, if we do a remove on the employee2 object, group2 object also gets deleted since goup2 is associated with the employee2 object which is expected.
By defining, cascadeType as REMOVE in RegisteredGroup Class. group2 object also deletes the employee1 object since employee1 object is associated with group2 object which is ambiguous.
NOTE: It is recommended to avoid ManyToMany mappings as far as possible because of the cascading Remove ambiguity. Prefer defining 2 one to many relations between the classes.
employee1.getGroups().addGroup(group1);
employee1.getGroups().addGroup(group2);
employee2.getGroup().addGroup(group2);
group1.getEmployees().addEmployee(employee1);
group2.getEmployees().addEmployee(employee1);
group2.getEmployees().addEmployee(employee2);
session.persist(employee1);
session.persist(employee2); saves both the employee and device objects.
session.persist(group1);
session.persist(group2); is not required. Can be eliminated.
@JoinTable(name="EMPLOYEE_GROUPS",
joinColumns=@JoinColumn(name="EMP_ID"),
inverseJoinColumns=@JoinColumn(name="GROUP_ID")) :
Since the relation is many to many, we cannot maintain the foreign key neither in Employee table nor RegisteredGroup table. So, we need a third table called EMPLOYEE_GROUPS which is specially created to maintain mapping between these 2 tables. Here is how the schema looks like if we declare the above annotation in Employee Class
RegisteredGroup Class
@Entity public class RegisteredGroup { @Id @GeneratedValue(strategy=GenerationType.AUTO) private int groupId; @Column(name="GROUP_NAME") private String groupName; @ManyToMany(mappedBy="groups") private Collection<Employee> employees = new HashSet<>(); //getters and setters ... }
mappedBy="groups": Hints the hibernate that the mapping key column has already been declared in Employee class groups variable (explained above.) and it will be created when hibernate loads the Employee class.


Comments
Post a Comment