This tutorial walks you through the steps of mapping a one-to-one shared primary key unidirectional relationship in Spring Boot JPA and Hibernate with @PrimaryKeyJoinColumn

What you'll need

  • JDK 1.8 or later

  • Maven 3 or later

  • Your favorite IDE

You can create and init a new Spring Boot project by using Spring Initializr or your IDE

Following is the final project structure with all the files we would create

├── src
│   └── main
│       ├── java
│       │   └── com
│       │       └── hellokoding
│       │           └── jpa
│       │               ├── model
│       │               │   ├── IDCard.java
│       │               │   └── Person.java
│       │               ├── repository
│       │               │   └── PersonRepository.java
│       │               └── Application.java
│       └── resources
│           └── application.properties
└── pom.xml

The One to One Shared Primary Key unidirectional mapping would be implemented in IDCard.java

Project dependencies

We will use the following dependencies

  • spring-boot-starter-data-jpa provides Hibernate and autoconfigure Spring DataSource

  • mysql-connector-java provides MySQL Java Client

  • lombok for generating boilerplate-code

<dependency>  
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>  
<dependency>  
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>  
<dependency>  
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

One-To-One shared primary key relationship

One to one shared primary key relationship refers to the relationship between two tables A and B in which

  • One row of A may be linked with only one row of B, and vice versa

  • Both tables A and B use the same primary key

In this example, person and idcard tables have a one-to-one relationship. One person has only one id card, and one id card only belongs to one person

idcard.persion_id is a shared primary key and also a foreign key references to person.id

Define JPA and Hibernate Entities

Create Person and IDCard JPA Entities corresponding to person and idcard tables in the database

[Person.java]

package com.hellokoding.jpa.model;

import lombok.Getter;  
import lombok.Setter;

import javax.persistence.*;

@Getter
@Setter
@Entity
public class Person {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;

    public Person(String name) {
        this.name = name;
    }
}

[IDCard.java]

package com.hellokoding.jpa.model;

import lombok.Getter;  
import lombok.Setter;

import javax.persistence.*;  
import java.util.UUID;

@Getter @Setter
@Entity
public class IDCard {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int personId;

    @OneToOne(cascade = CascadeType.ALL, optional = false)
    @PrimaryKeyJoinColumn(name = "person_id", referencedColumnName = "id")
    private Person person;

    @Column(unique = true, nullable = false)
    private String code = UUID.randomUUID().toString();

    public IDCard(Person person) {
        this.person = person;
    }
}

@Entity annotation is required to specify a JPA and Hibernate entity

@Id annotation is required to specify the identifier property of the entity

@OneToOne defines a one-to-one relationship between 2 entities

@PrimaryKeyJoinColumn specifies the primary key that is used as a foreign key to the parent entity

unique = true enforces the unique constraint

Spring Data JPA Repository

Spring Data JPA contains some built-in Repository abstracting common functions based on EntityManager to work with database such as findAll, findById, save, delete, deleteById. All we need for this example is extends JpaRepository.

[PersonRepository.java]

package com.hellokoding.jpa.repository;

import com.hellokoding.jpa.model.Person;  
import org.springframework.data.jpa.repository.JpaRepository;

public interface PersonRepository extends JpaRepository<Person, Integer>{  
}

[IDCardRepository.java]

package com.hellokoding.jpa.repository;

import com.hellokoding.jpa.model.IDCard;  
import org.springframework.data.jpa.repository.JpaRepository;

public interface IDCardRepository extends JpaRepository<IDCard, Integer>{  
}

Application Properties

Configure the Spring Datasource JDBC URL, user name, and password of your local MySQL server in application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false  
spring.datasource.username=root  
spring.datasource.password=hellokoding  
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=create  
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect  
spring.jpa.generate-ddl=true  
spring.jpa.show-sql=true

Create the test database in your local MySQL server if not exists

We don't have to create table schemas, the ddl-auto=create config allows JPA and
Hibernate does that based on the entity-relationship mappings. In practice, consider to use ddl-auto=none (default) and use a migration tool such as Flyway for better database management

spring.jpa.show-sql=true for showing generated SQL queries in the application logs, consider to disable it on production environment

Creating data with JPA and Hibernate

[Application.java]

package com.hellokoding.jpa;

import com.hellokoding.jpa.model.IDCard;  
import com.hellokoding.jpa.model.Person;  
import com.hellokoding.jpa.repository.IDCardRepository;  
import com.hellokoding.jpa.repository.PersonRepository;  
import lombok.RequiredArgsConstructor;  
import org.springframework.boot.CommandLineRunner;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.transaction.Transactional;  
import java.util.Arrays;

@RequiredArgsConstructor
@SpringBootApplication
public class Application implements CommandLineRunner {  
    private final IDCardRepository idCardRepository;
    private final PersonRepository personRepository;

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

    @Override
    @Transactional
    public void run(String... strings) {
        Person p1 = new Person("Tom");
        Person p2 = new Person("Daisy");
        Person p3 = new Person("Alex");
        personRepository.saveAll(Arrays.asList(p1, p2, p3));

        idCardRepository.save(new IDCard(p1));
        idCardRepository.save(new IDCard(p2));
        idCardRepository.save(new IDCard(p3));
    }
}

Run and test

Type the below command at the project root directory

mvn clean spring-boot:run

Access to your local MySQL Server to query the schema and data created by JPA/Hibernate based on your mapping

mysql> describe person;  
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | YES  |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> describe idcard;  
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| code      | varchar(255) | NO   | UNI | NULL    |       |
| person_id | int(11)      | NO   | PRI | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> select * from person;  
+----+-------+
| id | name  |
+----+-------+
|  1 | Tom   |
|  2 | Daisy |
|  3 | Alex  |
+----+-------+
3 rows in set (0.00 sec)

mysql> select * from idcard;  
+--------------------------------------+-----------+
| code                                 | person_id |
+--------------------------------------+-----------+
| 50ed2744-1ee9-4242-bccf-7a0a92af4ce3 |         1 |
| 244aa0bb-4188-4452-a74a-5d8e9e620c98 |         2 |
| 39c227da-520f-4c97-bc2d-1e6ebb62916f |         3 |
+--------------------------------------+-----------+
3 rows in set (0.00 sec)

Conclusion

In this tutorial, we learned to map a JPA and Hibernate bidirectional One to One foreign key in Spring Boot and MySQL. You can find the source code on Github

See also