Persistence top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we'll have a look at different ways of creating one-to-one mappings in JPA.

We'll need a basic understanding of the Hibernate framework, so please check out our Guide to Hibernate 5 with Spring for extra background.

Further reading:

Overview of JPA/Hibernate Cascade Types

A quick and practical overview of JPA/Hibernate Cascade Types.

Hibernate One to Many Annotation Tutorial

In this tutorial we'll have a look at the one-to-many mapping using JPA annotations with a practical example.

2. Description

Let's suppose we are building a User Management System and our boss asks us to store a mailing address for each user. A user will have one mailing address, and a mailing address will have only one user tied to it.

This is an example of a one-to-one relationship, in this case between user and address entities.

Let's see how we can implement this in the subsequent sections.

3. Using a Foreign Key

3.1. Modeling with a Foreign Key

Let's have a look at the following ER diagram which represents a foreign key based one-to-one mapping:

An ER Diagram mapping Users to Addresses via an address_id foreign key

In this example, the address_id column in users is the foreign key to address.

3.2. Implementing with a Foreign Key in JPA

First, let's create the User class and annotate it appropriately:

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

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;

    // ... getters and setters
}

Note that we place the @OneToOne annotation on the related entity field, Address.

Also, we need to place the @JoinColumn annotation to configure the name of the column in the users table that maps to the primary key in the address table. If we don't provide a name, then Hibernate will follow some rules to select a default one.

Finally, note in the next entity that we won't use the @JoinColumn annotation there. This is because we only need it on the owning side of the foreign key relationship. Simply put, whoever owns the foreign key column gets the @JoinColumn annotation.

The Address entity turns out a bit simpler:

@Entity
@Table(name = "address")
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    //...

    @OneToOne(mappedBy = "address")
    private User user;

    //... getters and setters
}

We also need to place the @OneToOne annotation here, too. That's because this is a bidirectional relationship. The address side of the relationship is called the non-owning side. 

4. Using a Shared Primary Key

4.1. Modeling with a Shared Primary Key

In this strategy, instead of creating a new column address_id, we'll mark the primary key column (user_id) of the address table as the foreign key to the users table:

An ER diagram with Users Tied to Addresses where they share the same primary key values

We've optimized the storage space by utilizing the fact that these entities have a one-to-one relationship between them.

4.2. Implementing with a Shared Primary Key in JPA

Notice that our definitions change only slightly:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    //...

    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
    private Address address;

    //... getters and setters
}
@Entity
@Table(name = "address")
public class Address {

    @Id
    @Column(name = "id")
    private Long id;

    //...

    @OneToOne
    @MapsId
    private User user;
   
    //... getters and setters
}

@MapsId tells Hibernate to use the id column of address as both primary key and foreign key. Also, notice that the @Id column of the Address entity no longer uses the @GeneratedValue annotation.

The mappedBy attribute is now moved to the User class since the foreign key is now present in the address table.

5. Using a Join Table

One-to-one mappings can be of two types – Optional and Mandatory. So far, we've seen only mandatory relationships.

Now, let's see imagine that our employees get associated with a workstation. It's one-to-one, but sometimes an employee might not have a workstation and vice-versa.

5.1. Modeling with a Join Table

The strategies that we have discussed until now force us to put null values in the column to handle optional relationships.

Typically, we think of many-to-many relationships when we consider a join table, but, using a join table, in this case, can help us to eliminate these null values:

An ER diagram relating Employees to Workstations via a Join Table

Now, whenever we have a relationship, we'll make an entry in the emp_workstation table and avoid nulls altogether.

5.2. Implementing with a Join Table in JPA

Our first example used @JoinColumn. This time, we'll use @JoinTable:

@Entity
@Table(name = "employee")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    //...

    @OneToOne(cascade = CascadeType.ALL)
    @JoinTable(name = "emp_workstation", 
      joinColumns = 
        { @JoinColumn(name = "employee_id", referencedColumnName = "id") },
      inverseJoinColumns = 
        { @JoinColumn(name = "workstation_id", referencedColumnName = "id") })
    private WorkStation workStation;

    //... getters and setters
}
@Entity
@Table(name = "workstation")
public class WorkStation {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    //...

    @OneToOne(mappedBy = "workStation")
    private Employee employee;

    //... getters and setters
}

@JoinTable instructs Hibernate to employ the join table strategy while maintaining the relationship.

Also, Employee is the owner of this relationship as we chose to use the join table annotation on it.

6. Conclusion

In this tutorial, we learned different ways of maintaining a one-to-one association in JPA and Hibernate and when to use each.

The source code of this tutorial can be found over on GitHub.

Persistence bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!