In my last blog we saw example of One to Many association mapping. In this example we are going to discuss about One to One association mapping. Also we ll see example of unidirectional and bidirectional mapping and how it works with hibernate mapping.
Lets consider a simple example of two entities having one to one relationship.
[Employee] 1___________________1 [EmployeeDetails]
So our java classes looks like
Employee.java package com.persistance.domain; public class Employee { private long id; private String name; private EmployeeDetails employeeDetails; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public EmployeeDetails getEmployeeDetails() { return employeeDetails; } public void setEmployeeDetails(EmployeeDetails employeeDetails) { this.employeeDetails = employeeDetails; } @Override public String toString() { return "Employee [id=" + id + ", name=" + name + "]"; } }
EmployeeDetails.java
package com.persistance.domain; public class EmployeeDetails { private long id; private String address; private int age; private String language; private Employee employee; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } public Employee getEmployee() { return employee; } public void setEmployee(Employee employee) { this.employee = employee; } @Override public String toString() { return "EmployeeDetails [id=" + id + ", adress=" + address + ", age=" + age + ", language=" + language + "]"; } }
One to One Unidirectional Mapping
Since we are considering one to one mapping but unidirectional, i.e. our example we can access EmployeeDetails from Employee but not reverse case. So our mapping files will be:
Employee.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.persistance.domain"> <class name="Employee" table="EMPLOYEE"> <id name="id" column="EMP_ID"> <generator/> </id> <property name="name" column="NAME"/> <many-to-one name="employeeDetails" column="DETAIL_ID" cascade="all" unique="true" not-null="true"/> </class> </hibernate-mapping>
We have used many to one with many-to-one with unique=true to ensure it one to one. Now since we are making to unidirectional hence we are not going to indicate anything in mapping file for EmployeeDetails.
EmployeeDetails.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.persistance.domain"> <class name="EmployeeDetails" table="EMPLOYEE_DETAILS"> <id name="id" column="EMPDETAIL_ID"> <generator/> </id> <property name="address" column="ADDRESS"/> <property name="age" column="AGE"/> <property name="language" column="LANGUAGE"/> </class> </hibernate-mapping>
Now it time to write Main Class to test this.
OneToOneAssociationTest.java
package com.persistance.main; import org.hibernate.Session; import com.persistance.domain.Employee; import com.persistance.domain.EmployeeDetails; import com.persistance.util.HibernateUtil; public class OneToOneAssociationTest { public static void main(String[] args) { Employee emp = new Employee(); emp.setName("B Mishra"); EmployeeDetails details = new EmployeeDetails(); details.setAge(24); details.setLanguage("Hindi"); details.setAddress("Bangalore"); details.setEmployee(emp); emp.setEmployeeDetails(details); Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.save(emp); session.getTransaction().commit(); } }
Time to run this class
OUTPUT
Hibernate: insert into EMPLOYEE_DETAILS (ADDRESS, AGE, LANGUAGE) values (?, ?, ?) Hibernate: insert into EMPLOYEE (NAME, DETAIL_ID) values (?, ?)
As we see here two records got inserted[actually even table got created in MySQL Database], one into EmployeeDetails with columns (ADDRESS,AGE,LANGUAGE) and in Person table our columns are (NAME,DETAIL_ID) where DETAIL_ID actually contains ID of EmployeeDetails record.
So our table shows
Now lets test the approachability from the java code.
OneToOneGetAssociationTest.java
package com.persistance.main; import org.hibernate.Session; import com.persistance.domain.Employee; import com.persistance.domain.EmployeeDetails; import com.persistance.util.HibernateUtil; public class OneToOneGetAssociationTest { public static void main(String[] args) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); System.out.println("Loading employee with id 1"); Employee employee = (Employee)session.load(Employee.class, new Long(1)); System.out.println(employee); System.out.println("Fetching its details"); System.out.println(employee.getEmployeeDetails()); System.out.println("Loading employee details with id 2"); EmployeeDetails details = (EmployeeDetails)session.load(EmployeeDetails.class, new Long(2)); System.out.println(details); System.out.println("Fetching it employee"); System.out.println(details.getEmployee()); } }
OUTPUT
Loading employee with id 1 Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.EMP_ID=? Employee [id=1, name=B Mishra] Fetching its details Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_0_, employeede0_.ADDRESS as ADDRESS4_0_, employeede0_.AGE as AGE4_0_, employeede0_.LANGUAGE as LANGUAGE4_0_ from EMPLOYEE_DETAILS employeede0_ where employeede0_.EMPDETAIL_ID=? EmployeeDetails [id=1, adress=Bangalore, age=24, language=Hindi] Loading employee details with id 2 Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_0_, employeede0_.ADDRESS as ADDRESS4_0_, employeede0_.AGE as AGE4_0_, employeede0_.LANGUAGE as LANGUAGE4_0_ from EMPLOYEE_DETAILS employeede0_ where employeede0_.EMPDETAIL_ID=? EmployeeDetails [id=2, adress=Bangalore, age=24, language=Hindi] Fetching it employee null
What we see here is
1. Employee Object gets loaded [See select sql executiong on Employee Table]
Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.EMP_ID=?
2. Hibernate fires another query to fetch its associated object when we call employee.getEmployeeDetails().
Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_0_, employeede0_.ADDRESS as ADDRESS4_0_, employeede0_.AGE as AGE4_0_, employeede0_.LANGUAGE as LANGUAGE4_0_ from EMPLOYEE_DETAILS employeede0_ where employeede0_.EMPDETAIL_ID=?
3. After this step, we try to load EmployeeDetails independently which happens and hibernate fires another sql for it
Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_0_, employeede0_.ADDRESS as ADDRESS4_0_, employeede0_.AGE as AGE4_0_, employeede0_.LANGUAGE as LANGUAGE4_0_ from EMPLOYEE_DETAILS employeede0_ where employeede0_.EMPDETAIL_ID=?
4. Now we try to load association of the EmployeeDetails but hibernate does not do anything since in mapping file of EmployeeDetails it no idea how to get a association from any where, so the details.getEmployee() return null.
Note : I took diff ID of Employee and EmployeeDetails to show example how hibernate fire sql statement in backgroud. It does not hits database if it founds the object already present in it cache. So in this example if we use ID=1 for the employee details the OUTPUT will be
Loading employee with id 1 Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.EMP_ID=? Employee [id=1, name=B Mishra] Fetching its details Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_0_, employeede0_.ADDRESS as ADDRESS4_0_, employeede0_.AGE as AGE4_0_, employeede0_.LANGUAGE as LANGUAGE4_0_ from EMPLOYEE_DETAILS employeede0_ where employeede0_.EMPDETAIL_ID=? EmployeeDetails [id=1, adress=Bangalore, age=24, language=Hindi] Loading employee details with id 1 EmployeeDetails [id=1, adress=Bangalore, age=24, language=Hindi] Fetching it employee null
Caching : You see Hibernate has did not hit database as the EmployeeDetails object was already present in cache which it has fetched for Employee so not DB operation.
Bidirectional Mapping
We are going to use the same example and modify its hibernate mapping files to make it bidirectional. Before that we will drop the EMPLOYEE and EMPLOYEE_DETAILS table so that it get created again with new definition.
Employee.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.persistance.domain"> <class name="Employee" table="EMPLOYEE"> <id name="id" column="EMP_ID"> <generator/> </id> <property name="name" column="NAME"/> <many-to-one name="employeeDetails" column="DETAIL_ID" cascade="all" unique="true" not-null="true"/> </class> </hibernate-mapping>
EmployeeDetails.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.persistance.domain"> <class name="EmployeeDetails" table="EMPLOYEE_DETAILS"> <id name="id" column="EMPDETAIL_ID"> <generator/> </id> <property name="address" column="ADDRESS"/> <property name="age" column="AGE"/> <property name="language" column="LANGUAGE"/> <one-to-one name="employee" property-ref="employeeDetails" cascade="all"/> </class> </hibernate-mapping>
Lets change our main class to inser two record this time.
OneToOneAssociationTest.java
package com.persistance.main; import org.hibernate.Session; import com.persistance.domain.Employee; import com.persistance.domain.EmployeeDetails; import com.persistance.util.HibernateUtil; public class OneToOneAssociationTest { public static void main(String[] args) { Employee emp1 = new Employee(); emp1.setName("A Mishra"); EmployeeDetails details1 = new EmployeeDetails(); details1.setAge(24); details1.setLanguage("Hindi"); details1.setAddress("Bangalore"); details1.setEmployee(emp1); emp1.setEmployeeDetails(details1); Employee emp2 = new Employee(); emp2.setName("B Mishra"); EmployeeDetails details2 = new EmployeeDetails(); details2.setAge(26); details2.setLanguage("English"); details2.setAddress("Mumbai"); details2.setEmployee(emp2); emp2.setEmployeeDetails(details2); Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.save(emp1); session.save(emp2); session.getTransaction().commit(); } }
OUTPUT
Hibernate: insert into EMPLOYEE_DETAILS (ADDRESS, AGE, LANGUAGE) values (?, ?, ?) Hibernate: insert into EMPLOYEE (NAME, DETAIL_ID) values (?, ?) Hibernate: insert into EMPLOYEE_DETAILS (ADDRESS, AGE, LANGUAGE) values (?, ?, ?) Hibernate: insert into EMPLOYEE (NAME, DETAIL_ID) values (?, ?)
Tables
So we see here its the same hibernate operations as it was in earlier example. Even the table looks same with same columns, as shown in tables images in previous test run. So what have changed actually. Lets run the class OneToOneGetAssociationTest.java and see.
package com.persistance.main; import org.hibernate.Session; import com.persistance.domain.Employee; import com.persistance.domain.EmployeeDetails; import com.persistance.util.HibernateUtil; public class OneToOneGetAssociationTest { public static void main(String[] args) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); System.out.println("Loading employee with id 1"); Employee employee = (Employee)session.load(Employee.class, new Long(1)); System.out.println(employee); System.out.println("Fetching its details"); System.out.println(employee.getEmployeeDetails()); System.out.println("Loading employee details with id 2"); EmployeeDetails details = (EmployeeDetails)session.load(EmployeeDetails.class, new Long(2)); System.out.println(details); System.out.println("Fetching it employee"); System.out.println(details.getEmployee()); } }
OUTPUT
Loading employee with id 1 Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.EMP_ID=? Employee [id=1, name=A Mishra] Fetching its details Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_1_, employeede0_.ADDRESS as ADDRESS4_1_, employeede0_.AGE as AGE4_1_, employeede0_.LANGUAGE as LANGUAGE4_1_, employee1_.EMP_ID as EMP1_3_0_, employee1_.NAME as NAME3_0_, employee1_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE_DETAILS employeede0_ left outer join EMPLOYEE employee1_ on employeede0_.EMPDETAIL_ID=employee1_.DETAIL_ID where employeede0_.EMPDETAIL_ID=? Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.DETAIL_ID=? EmployeeDetails [id=1, adress=Bangalore, age=24, language=Hindi] Loading employee details with id 2 Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_1_, employeede0_.ADDRESS as ADDRESS4_1_, employeede0_.AGE as AGE4_1_, employeede0_.LANGUAGE as LANGUAGE4_1_, employee1_.EMP_ID as EMP1_3_0_, employee1_.NAME as NAME3_0_, employee1_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE_DETAILS employeede0_ left outer join EMPLOYEE employee1_ on employeede0_.EMPDETAIL_ID=employee1_.DETAIL_ID where employeede0_.EMPDETAIL_ID=? EmployeeDetails [id=2, adress=Mumbai, age=26, language=English] Fetching it employee Employee [id=2, name=B Mishra]
So what we see here
1. Hibernate fire select on Employee table to get Employee
Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.EMP_ID=? Employee [id=1, name=A Mishra]
2. When we call employee.getEmployeeDetails(), it fires another query to get its association, but using left outer join
Fetching its details Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_1_, employeede0_.ADDRESS as ADDRESS4_1_, employeede0_.AGE as AGE4_1_, employeede0_.LANGUAGE as LANGUAGE4_1_, employee1_.EMP_ID as EMP1_3_0_, employee1_.NAME as NAME3_0_, employee1_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE_DETAILS employeede0_ left outer join EMPLOYEE employee1_ on employeede0_.EMPDETAIL_ID=employee1_.DETAIL_ID where employeede0_.EMPDETAIL_ID=? Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.DETAIL_ID=? EmployeeDetails [id=1, adress=Bangalore, age=24, language=Hindi]
3. Now when we load EmployeeDetails with ID=2 it load the EmployeeDetails, again using left outer join
Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_1_, employeede0_.ADDRESS as ADDRESS4_1_, employeede0_.AGE as AGE4_1_, employeede0_.LANGUAGE as LANGUAGE4_1_, employee1_.EMP_ID as EMP1_3_0_, employee1_.NAME as NAME3_0_, employee1_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE_DETAILS employeede0_ left outer join EMPLOYEE employee1_ on employeede0_.EMPDETAIL_ID=employee1_.DETAIL_ID where employeede0_.EMPDETAIL_ID=? EmployeeDetails [id=2, adress=Mumbai, age=26, language=English] Fetching it employee Employee [id=2, name=B Mishra]
So Actually we have achieved bidirectional mapping.
But the problem here is in our table EmployeeDetails there is no FK for Employee and hence hibernate uses left outer join to get the association, also in case of traversing from EmployeeDetail Hibernate Pre loads the Employee objects using left outer join , which can be a expensive operation in case of large amount of EmployeeDetails is loaded. Joins are anyways expensive.
SOLUTION
Now how solve this problem. So the solution is here by changin the Mapping Files a bit, we are not changing the Employee.hbm.xml but some changes are required in
EmployeeDetails.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.persistance.domain"> <class name="EmployeeDetails" table="EMPLOYEE_DETAILS"> <id name="id" column="EMPDETAIL_ID"> <generator/> </id> <property name="address" column="ADDRESS"/> <property name="age" column="AGE"/> <property name="language" column="LANGUAGE"/> <many-to-one name="employee" column="EMP_ID" cascade="all" unique="true"/> </class> </hibernate-mapping>
So actually we have done same as Employee class was mapped. Lets drop the tables and run the class OneToOneAssociationTest.java again.
OUTPUT
Hibernate: insert into EMPLOYEE_DETAILS (ADDRESS, AGE, LANGUAGE, EMP_ID) values (?, ?, ?, ?) Hibernate: insert into EMPLOYEE (NAME, DETAIL_ID) values (?, ?) Hibernate: insert into EMPLOYEE_DETAILS (ADDRESS, AGE, LANGUAGE, EMP_ID) values (?, ?, ?, ?) Hibernate: insert into EMPLOYEE (NAME, DETAIL_ID) values (?, ?) Hibernate: update EMPLOYEE_DETAILS set ADDRESS=?, AGE=?, LANGUAGE=?, EMP_ID=? where EMPDETAIL_ID=? Hibernate: update EMPLOYEE_DETAILS set ADDRESS=?, AGE=?, LANGUAGE=?, EMP_ID=? where EMPDETAIL_ID=?
Here we see that one new operation is happening which is its updating EmployeeDetail as EmployeeDetail was persisted before Employee so once Employee is persisted EmployeeDetail’s EMP_ID is updated by the hibernate.
Tables
And to see how efficiently it access the association lets run the class OneToOneGetAssociationTest.java
OUTPUT
Loading employee with id 1 Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.EMP_ID=? Employee [id=1, name=A Mishra] Fetching its details Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_0_, employeede0_.ADDRESS as ADDRESS4_0_, employeede0_.AGE as AGE4_0_, employeede0_.LANGUAGE as LANGUAGE4_0_, employeede0_.EMP_ID as EMP5_4_0_ from EMPLOYEE_DETAILS employeede0_ where employeede0_.EMPDETAIL_ID=? EmployeeDetails [id=1, adress=Bangalore, age=24, language=Hindi] Loading employee details with id 2 Hibernate: select employeede0_.EMPDETAIL_ID as EMPDETAIL1_4_0_, employeede0_.ADDRESS as ADDRESS4_0_, employeede0_.AGE as AGE4_0_, employeede0_.LANGUAGE as LANGUAGE4_0_, employeede0_.EMP_ID as EMP5_4_0_ from EMPLOYEE_DETAILS employeede0_ where employeede0_.EMPDETAIL_ID=? EmployeeDetails [id=2, adress=Mumbai, age=26, language=English] Fetching it employee Hibernate: select employee0_.EMP_ID as EMP1_3_0_, employee0_.NAME as NAME3_0_, employee0_.DETAIL_ID as DETAIL3_3_0_ from EMPLOYEE employee0_ where employee0_.EMP_ID=? Employee [id=2, name=B Mishra]
See NO JOINS 🙂 and object are loaded only when association is accessed.
Hope this discussion will help to understand One to One mapping and association fetching behavior of hibernate in more detail.
Other Class and Mapping Used
HibernateUti.java
package com.persistance.util; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { private static final SessionFactory sessionFactory = builSessionFactory(); private static SessionFactory builSessionFactory() { try{ return new Configuration().configure("com/persistance/resources/hibernate.cfg.xml").buildSessionFactory(); } catch (Throwable e) { System.err.println("Initialisation of Session factory failed"); throw new ExceptionInInitializerError(e); } } public static SessionFactory getSessionFactory() { return sessionFactory; } }
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/bagish</property> <property name="connection.username">root</property> <property name="connection.password">welcome</property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <!-- Enable Hibernate's automatic session context management --> <property name="current_session_context_class">thread</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">update</property> <mapping resource="com/persistance/resources/Employee.hbm.xml"/> <mapping resource="com/persistance/resources/EmployeeDetails.hbm.xml"/> </session-factory> </hibernate-configuration>