In Hibernate or any ORM framework, a java class is mapped to a database table.
This means the fields of the java class correspond to the columns of a database table.
But what if two java classes need to be mapped to a single database table.
Scenario
Consider an entity Employee which represents a typical employee in an organization.
Information about an employee can be split into 2 parts :
1. Basic information such as his employee id and name.
2. Advanced information which provides more details about the employee such as his address, blood group, country etc.
Many times only basic attributes of an employee are required across the organization system.
Thus, it makes sense to put this information about the employee in one class so that the class(and its objects) are light weight.
This makes it easy to transfer the objects of this class across the entire system.
Keeping in mind the above scenario, an employee can be represented using 2 classes :
Employee : Contains his employee id and name
EmployeeDetail : Has other fields such as his address, blood group, country etc.
Also, while saving the employee in the database, there is no need to have 2 separate tables which means both the classes should be mapped to one table.
But, How is that possible since one class is mapped to one database table. It is !!!
Mapping 2 classes to 1 Table
As per the above scenario, we have 2 classes Employee
and EmployeeDetail
.
Employee class is the main entity and is associated with a database table.
EmployeeDetail
is associated with Employee but does not have a corresponding database table.
Employee and EmployeeDetail
classes share a Has A relationship meaning Employee
class has a field of type EmployeeDetail
.
Now, how should these be arranged so that when they are saved, the record is inserted into only 1 table.
Hibernate provides two annotations which serve this purpose.
These annotations are:
@Embeddable
Applied over the class which is not directly mapped with a database table but rather it is associated with another class, which, in turn is the actual entity class.
It is used as an instance variable inside the main entity class.
According to the description, this annotation should be applied over EmployeeDetail
class.
From official documentation of this annotation
Each of the persistent properties or fields of the embedded object is mapped to the database table for the entity.
@Embedded
Applied over the instance variable of the Embeddable class. This instance variable is placed in the actual entity class.In our case, it will be applied over the instance variable(field) of type EmployeeDetail
in Employee
class.
From official documentation of this annotation
Specifies a persistent field or property of an entity whose value is an instance of an embeddable class.
After applying the annotations, the classes will look as below.
Employee.java
import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; /** * Main entity class which is mapped to the database table */ @Entity public class Employee { @Id @GeneratedValue private int id; private String name; private String employeeId; /** * Sub entity which is mapped to the same database table */ @Embedded private EmployeeDetail empDetails; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmployeeId() { return employeeId; } public void setEmployeeId(String employeeId) { this.employeeId = employeeId; } public EmployeeDetail getEmpDetails() { return empDetails; } public void setEmpDetails(EmployeeDetail empDetails) { this.empDetails = empDetails; } }
EmployeeDetail.java
import javax.persistence.Embeddable; /** * Class which is mapped to the database table of its owing entity class */ @Embeddable public class EmployeeDetail { private String bloodGroup; private int age; private String country; public String getBloodGroup() { return bloodGroup; } public void setBloodGroup(String bloodGroup) { this.bloodGroup = bloodGroup; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } }
In order to insert an employee record, we need to create objects of both these classes and save them using Hibernate classes. The code is given below.
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; public class Main { public static void main(String[] args) { //create configuration object Configuration configuration = new Configuration().configure(); //add class so that it is recognized as entity by hibernate configuration.addAnnotatedClass(Employee.class); StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() .applySettings(configuration.getProperties()).build(); //create a session factory SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry); Session session = sessionFactory.openSession(); //first we need to create EmployeeDetail object EmployeeDetail detail = new EmployeeDetail(); //set details detail.setAge(25); detail.setCountry("India"); detail.setBloodGroup("B+"); //create Employee object Employee employee = new Employee(); //set employee info employee.setName("A B"); employee.setEmployeeId("EMP001"); //set detail object in main entity employee.setEmpDetails(detail); //save employee session.save(employee); //clean up resources session.flush(); session.close(); sessionFactory.close(); } }
Output
When this code is executed, we can see the below logs in console.
Logs demonstrate that an insert query is executed.
The query has all attributes of both the classes combined together and is executed on the Employee table only.
Hibernate: create table Employee (id integer not null, age integer not null, bloodGroup varchar(255), country varchar(255), employeeId varchar(255), name varchar(255), primary key (id))
Before executing the code, place a file Hibernate.cfg.xml with following contents in the classpath.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/employee</property> <property name="connection.username">root</property> <property name="connection.password">root</property> <property name="show_sql">true</property> <property name="hbm2ddl.auto">update</property> </session-factory> </hibernate-configuration>
This is the configuration file required by Hibernate which tells it the details of the database to which Hibernate should connect and perform operations.
Let’s tweak in
- If neither
@Embedded
annotation is applied over the sub-entity class and@Embedded
annotation is applied over the field the sub-entity class inside main entity class, then following error is raisedException in thread “main” org.hibernate.MappingException: Could not determine type for: EmployeeDetail, at table: Employee, for columns: [org.hibernate.mapping.Column(empDetails)]
- If Hibernate.cfg.xml is not found in the classpath, then the following error will be raised.
Exception in thread “main” org.hibernate.internal.util.config.ConfigurationException: Could not locate cfg.xml resource [hibernate.cfg.xml]
- It is not mandatory to name the configuration file as Hibernate.cfg.xml.
You can name it as per your choice but you need to inform Hibernate about it.
This is done by providing the file name in theconfigure
method oforg.Hibernate.cfg.Configuration
class asnew Configuration().configure("fileName")
More on Hibernate configuration here.