In this tutorial, we’ll how to implement one to one relationship mapping in JPA with database table and schema example. There might be different use case to implementing one to one relationship. For example, one user will have only one user profile.

One-to-One Relationship in JPA Spring Boot

Lets say we are developing a User Management System and we want to add mailing address feature. Every user will have only have one mailing address. So in this case, we need to create one-to-one relationship between user and address entity or tables. Learn How to Connect PostgreSQL Database in Spring Boot Project.

How to Implement one-to-one relatio...
How to Implement one-to-one relationship Mapping in JPA - Spring Boot

We can implement one-to-one mapping in 3 different ways:

  1. Using Foreign Key
  2. Using Shared Primary Key
  3. Using Join Table

1. Using Foreign Key

Lets create a User class with table name users.

@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
}

If you see above, we have added @OneToOne annotation on address field. 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.

@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
}

In address entity, we have also added @OnetoOne for bidirectional relationship.

Bellow is table definition created from above entities using foreign key:

                           Table "users"
   Column   |            Type             | Collation | Nullable | Default 
------------+-----------------------------+-----------+----------+---------
 id         | bigint                      |           | not null | 
 email      | character varying(255)      |           | not null | 
 first_name | character varying(255)      |           |          | 
 last_name  | character varying(255)      |           |          | 
 address_id | bigint                      |           |          | 
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "uk_6dotkott2kjsp8vw4d0m25fb7" UNIQUE CONSTRAINT, btree (email)
Foreign-key constraints:
    "fkditu6lr4ek16tkxtdsne0gxib" FOREIGN KEY (address_id) REFERENCES address(id)
                      Table "address"
 Column |          Type          | Collation | Nullable | Default 
--------+------------------------+-----------+----------+---------
 id     | bigint                 |           | not null | 
 name   | character varying(255) |           |          | 
Indexes:
    "address_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "users" CONSTRAINT "fkditu6lr4ek16tkxtdsne0gxib" FOREIGN KEY (address_id) REFERENCES address(id)

2. One-to-One Relationship using Shared Primary Key

In this one to one 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.

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

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

    //...

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

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

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

    //...

    @OneToOne
    @MapsId
    @JoinColumn(name = "user_id")
    private User user;
   
    //... getters and setters
}

The mappedBy attribute is now in User class since the foreign key is now present in the address table. We have also added the @PrimaryKeyJoinColumn annotation, which indicates that the primary key of the User entity is used as the foreign key value for the associated Address entity. Also @MapsId annotation, which indicates that the primary key values will be copied from the User entity.

Bellow is table definition created from above entities using shared primary key:

                           Table "users"
   Column   |            Type             | Collation | Nullable | Default 
------------+-----------------------------+-----------+----------+---------
 id         | bigint                      |           | not null | 
 email      | character varying(255)      |           | not null | 
 first_name | character varying(255)      |           |          | 
 last_name  | character varying(255)      |           |          | 
 password   | character varying(255)      |           | not null | 
 role       | character varying(255)      |           |          | 
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "uk_6dotkott2kjsp8vw4d0m25fb7" UNIQUE CONSTRAINT, btree (email)
Referenced by:
    TABLE "address" CONSTRAINT "fk6i66ijb8twgcqtetl8eeeed6v" FOREIGN KEY (user_id) REFERENCES users(id)
                      Table "address"
 Column  |          Type          | Collation | Nullable | Default 
---------+------------------------+-----------+----------+---------
 user_id | bigint                 |           | not null | 
 name    | character varying(255) |           |          | 
Indexes:
    "address_pkey" PRIMARY KEY, btree (user_id)
Foreign-key constraints:
    "fk6i66ijb8twgcqtetl8eeeed6v" FOREIGN KEY (user_id) REFERENCES users(id)

I personally like to implement this relationship in my projects.

3. Using Join Table in JPA

Lets create relationship using @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
}