There are several ways to map a one-to-one relationship with JPA and Hibernate, in this tutorial we will look at some of them.
One to One Relationship
In a one-to-one relationship, a record in a table is associated with a single record in another table, for example an Employee can correspond to only one EmployeeDetails and vice versa.
Configuration
Dependencies
The dependencies for this project are clearly Hibernate and finally the H2 database.
<dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>6.2.4.Final</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.1.214</version> </dependency> </dependencies>
Hibernate
Let’s create the persistence.xml file, which is nothing more than the JPA persistence configuration. In this file we are going to define all the database connection parameters, we are also going to define where our entities are located.
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> <persistence-unit name="default"> <class>com.lorenzomiscoli.hibernate_one_to_one.model.Employee</class> <class>com.lorenzomiscoli.hibernate_one_to_one.model.EmployeeDetails</class> <properties> <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver" /> <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:hibernate_one_to_one" /> <property name="jakarta.persistence.jdbc.user" value="sa" /> <property name="jakarta.persistence.jdbc.password" value="" /> <property name="hibernate.hbm2ddl.auto" value="update" /> </properties> </persistence-unit> </persistence>
Foreign key mapping
Diagram
Let’s look at the ER diagram which is based on a foreign key mapping.
In this example, the employee_id column of the employee_details table is the foreign key of employee.
Entity
Let’s create our first Employee entity.
@Entity @Table(name = "employee") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @OneToOne(mappedBy = "employee", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) private EmployeeDetails employeeDetails; // getters and setters public void setDetails(EmployeeDetails employeeDetails) { if (employeeDetails == null) { if (this.employeeDetails != null) { this.employeeDetails.setEmployee(null); } } else { employeeDetails.setEmployee(this); } this.employeeDetails = employeeDetails; } }
With the @OneToOne annotation we configure a one to one relationship, we have also inserted the mappedBy attribute, in this way we tell JPA that the relationship is already mapped on the other side of the relationship, that is on EmployeeDetails.
Now let’s create the EmployeeDetails entity.
@Entity @Table(name = "employee_details") public class EmployeeDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String department; private String address; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "employee_id") private Employee employee; // getters and setters }
We inserted the @OneToOne annotation again but this time we inserted the @JoinColumn annotation right after it. With this last annotation we specify which column holds the foreign key.
This type of mapping is not recommended in this case, as there can only be one employee_details associated with an employee it would make more sense for the primary key of employee_details to be the same as employee.
Shared key mapping
Diagram
This ER diagram, unlike the previous one, is based on a shared key. The employee_id column of the employee_details table is both the primary key and the foreign key.
Entity
Now let’s edit the EmployeeDetails entity.
@Entity @Table(name = "employee_details") public class EmployeeDetails { @Id private Integer id; private String department; private String address; @OneToOne(fetch = FetchType.LAZY) @MapsId private Employee employee; // getters and setters }
We have added the @MapsId annotation to the EmployeeDetails entity which indicates that the primary key values will be copied from the Employee entity, we have also removed the @JoinColumn annotation.
The mappings we’ve seen so far are bidirectional, which means that while the unidirectional @OneToOne association can be lazy-fetched, the parent side of a bidirectional @OneToOne association is not. Even when we specify the FetchType.LAZY attribute, the parent side association behaves like a FetchType.EAGER relationship. So in this case, if we make one query to retrieve an Employee, JPA will make a second one to also retrieve EmployeeDetails.
Unidirectional mapping with shared key
A great alternative is to make use of the @MapsId annotation only on the child entity and not map the parent entity at all.
This way you don’t need to have a bidirectional association since you can still find the EmployeeDetails entity by using using the Employee entity identifier.
Conclusion
In this article we have seen some ways to map a one to one relationship with JPA and Hibernate, and we have also seen how using a shared key is more convenient and performant in a one to one association.
The source code of this project is available on GitHub.