Hibernate is the most popular Object Relational Mapping (ORM) tool for Java. It implements Java Persistence API (JPA) specifications and has a huge industry adoption.

Hibernate maps the tables in your database to Entity classes in your application.

You can define relationships among these entities in the same way you define relationships among the tables in your database.

In this tutorial, You’ll learn how to define a one-to-one relationship between two entities using JPA and Hibernate.

Let’s consider an application that stores a lot of information about its users so that it can provide a personalized experience to them.

In such cases, it makes sense to store users primary details like name, email, password in a USERS table and store all the other secondary details in a separate table called USER_PROFILES, and have a one-to-one relationship between USERS and USER_PROFILES table.

Following is a visual of such a database schema -

JPA, Hibernate One to One Mapping Example Table Structure

The users and user_profiles tables exhibit a one-to-one relationship between each other. The relationship is mapped using a foreign key called user_id in the user_profiles table.

We’ll create a project from scratch and learn how to go about implementing such one-to-one relationship at the object level using JPA and Hibernate.

Let’s get started!

Creating the Project

Let’s use Spring Boot CLI to generate the Project. It’s the quickest way to bootstrap a Spring Boot project.

Fire up your terminal and type the following command to generate the project -

spring init -n=jpa-one-to-one-demo -d=web,jpa,mysql --package-name=com.example.jpa jpa-one-to-one-demo

Alternatively, You can generate the project from Spring Initializr web tool by following the instructions below -

  1. Head over to http://start.spring.io
  2. Click Switch to full version link to see all the options.
  3. Enter Artifact as “jpa-one-to-one-demo”
  4. Change Package Name to “com.example.jpa”
  5. Select Web, JPA and Mysql dependencies.
  6. Click Generate Project to generate and download the project.

Following is the directory structure of the project for your reference -

Hibernate Spring Boot JPA One to One Mapping example Directory Structure

Note that, the project you bootstrapped using one of the above methods won’t have model and repository packages and all the classes inside these packages at this point. But don’t worry, We’ll create them shortly.

Configuring the Database and Log levels

Since we’re using MySQL database, we need to configure the database URL, username, and password. Open src/main/resources/application.properties file and add the following properties -

# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_one_to_one_demo?useSSL=false
spring.datasource.username=root
spring.datasource.password=root

# Hibernate

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE

The hibernate property spring.jpa.hibernate.ddl-auto = update will automatically create/update the database tables as per the entity classes in your program on application startup.

The logging properties will help us debug SQL statements executed by hibernate.

Don’t forget to change spring.datasource.username and spring.datasource.password as per your MySQL installation. Also, Please create a database named jpa_one_to_one_demo before proceeding to the next section.

Defining the Entities

Let’s now define the entity classes that will be mapped to the tables we saw earlier.

1. User Entity

Create a new package called model inside com.example.jpa package and then create the User class inside model package with the following contents -

package com.example.jpa.model;

import org.hibernate.validator.constraints.Email;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

@Entity
@Table(name = "users")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Size(max = 65)
    @Column(name = "first_name")
    private String firstName;

    @Size(max = 65)
    @Column(name = "last_name")
    private String lastName;

    @NotNull
    @Email
    @Size(max = 100)
    @Column(unique = true)
    private String email;

    @NotNull
    @Size(max = 128)
    private String password;

    @OneToOne(fetch = FetchType.LAZY,
            cascade =  CascadeType.ALL,
            mappedBy = "user")
    private UserProfile userProfile;

    // Hibernate requires a no-arg constructor
    public User() {

    }

    public User(String firstName, String lastName, String email, String password) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.password = password;
    }

    // Getters and Setters (Omitted for brevity)
}

2. Gender Enum

The Gender enum will be used in the UserProfile class to store the gender information of users. Create Gender.java inside com.example.jpa.model package with the following contents -

package com.example.jpa.model;

public enum Gender {
    MALE,
    FEMALE
}

3. UserProfile Entity

Finally, Create the UserProfile class inside com.example.jpa.model package with the following contents -

package com.example.jpa.model;

import javax.persistence.*;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.Date;

@Entity
@Table(name = "user_profiles")
public class UserProfile implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "phone_number")
    @Size(max = 15)
    private String phoneNumber;

    @Enumerated(EnumType.STRING)
    @Column(length = 10)
    private Gender gender;

    @Temporal(TemporalType.DATE)
    @Column(name = "dob")
    private Date dateOfBirth;

    @Size(max = 100)
    private String address1;

    @Size(max = 100)
    private String address2;

    @Size(max = 100)
    private String street;

    @Size(max = 100)
    private String city;

    @Size(max = 100)
    private String state;

    @Size(max = 100)
    private String country;

    @Column(name = "zip_code")
    @Size(max = 32)
    private String zipCode;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    public UserProfile() {

    }

    public UserProfile(String phoneNumber, Gender gender, Date dateOfBirth, 
                       String address1, String address2, String street, String city, 
                       String state, String country, String zipCode) {
        this.phoneNumber = phoneNumber;
        this.gender = gender;
        this.dateOfBirth = dateOfBirth;
        this.address1 = address1;
        this.address2 = address2;
        this.street = street;
        this.city = city;
        this.state = state;
        this.country = country;
        this.zipCode = zipCode;
    }

    // Getters and Setters (Omitted for brevity)
}

Let’s now understand how the above entity classes define a one-to-one relationship.

A one-to-one relationship is defined using JPA’s @OneToOne annotation. It accepts several attributes. Let’s understand what those attributes are meant for -

  • fetch = FetchType.LAZY - Fetch the related entity lazily from the database.

  • cascade = CascadeType.ALL - Apply all cascading effects to the related entity. That is, whenever we update/delete a User entity, update/delete the corresponding UserProfile as well.

  • mappedBy = “user” - We use mappedBy attribute in the User entity to tell hibernate that the User entity is not responsible for this relationship and It should look for a field named user in the UserProfile entity to find the configuration for the JoinColumn/ForeignKey column.

In a bi-directional relationship, we specify @OneToOne annotation on both the entities but only one entity is the owner of the relationship. Most often, the child entity is the owner of the relationship and the parent entity is the inverse side of the relationship.

The owner of the relationship contains a @JoinColumn annotation to specify the foreign key column, and the inverse-side of the relationship contains a mappedBy attribute to indicate that the relationship is mapped by the other entity.

Defining the Repositories

Finally, Let’s define the Repositories for accessing the User and UserProfile details from the database.

We’ll extend the repositories from Spring-Data-JPA’s JpaRepository interface. Spring Data JPA already contains an implementation of JpaRepository interface called SimpleJpaRepository which is plugged in at runtime.

1. UserRepository

Create a new package called repository inside com.example.jpa package and then create the following interface inside repository package -

package com.example.jpa.repository;

import com.example.jpa.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

}

2. UserProfileRepository

Create an interface named UserProfileRepository inside com.example.jpa package with the following contents -

package com.example.jpa.repository;

import com.example.jpa.model.UserProfile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserProfileRepository extends JpaRepository<UserProfile, Long> {

}

That’s all we need to do in the repository layer. Thanks to SimpleJpaRepository, We can perform all the CRUD operations on the User and UserProfile entities without implementing anything.

Writing code to test the one-to-one Mapping

Let’s now write some code to test our setup. Open JpaOneToOneDemoApplication.java file and replace it with the following code -

package com.example.jpa;

import com.example.jpa.model.Gender;
import com.example.jpa.model.User;
import com.example.jpa.model.UserProfile;
import com.example.jpa.repository.UserRepository;
import com.example.jpa.repository.UserProfileRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.Calendar;

@SpringBootApplication
public class JpaOneToOneDemoApplication implements CommandLineRunner {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserProfileRepository userProfileRepository;

    public static void main(String[] args) {
        SpringApplication.run(JpaOneToOneDemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        // Clean up database tables
        userProfileRepository.deleteAllInBatch();
        userRepository.deleteAllInBatch();

        //=========================================

        // Create a User instance
        User user = new User("Rajeev", "Singh", "rajeev@callicoder.com",
                "MY_SUPER_SECRET_PASSWORD");

        Calendar dateOfBirth = Calendar.getInstance();
        dateOfBirth.set(1992, 7, 21);

        // Create a UserProfile instance
        UserProfile userProfile = new UserProfile("+91-8197882053", Gender.MALE, dateOfBirth.getTime(),
                "747", "2nd Cross", "Golf View Road, Kodihalli", "Bangalore",
                "Karnataka", "India", "560008");


        // Set child reference(userProfile) in parent entity(user)
        user.setUserProfile(userProfile);

        // Set parent reference(user) in child entity(userProfile)
        userProfile.setUser(user);

        // Save Parent Reference (which will save the child as well)
        userRepository.save(user);

        //=========================================
    }
}

I’ve implemented the CommandLineRunner interface in the main class. You can use it to run some code once the application has started. All the code of interest is in the run() method.

We first clean-up both the User and UserProfile tables, and then insert a User and the corresponding UserProfile object in the database.

Time to Run the Application

Open your terminal and type the following command from the root directory of the project to run the application -

mvn spring-boot:run

You can check the logs for debugging what statements are executed by hibernate. Hibernate executes the following SQL statements for performing the tasks that we have specified in the run() method -

org.hibernate.SQL : delete from user_profiles
org.hibernate.SQL : delete from users
org.hibernate.SQL : insert into users (email, first_name, last_name, password) values (?, ?, ?, ?)
org.hibernate.SQL : insert into user_profiles (address1, address2, city, country, dob, gender, phone_number, state, street, user_id, zip_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

Conclusion

That’s All Folks! In this article, you learned how to map a one-to-one database relationship at the object level in you program using JPA and Hibernate.

You can find the source code for the sample project that we build in this article in my jpa-hibernate-tutorials github repository.

Consider giving a star on github if you find the project useful. Also, Don’t forget to share this post with your friends and colleagues.

You might also wanna check out the following articles on JPA Mapping -

JPA / Hibernate One to Many mapping Example with Spring Boot

JPA / Hibernate Many to Many mapping Example with Spring Boot

Thanks for reading. See you next time! :)