This tutorial will walk you through the steps of mapping a JPA and Hibernate bidirectional One To One shared primary key with @MapsId in Spring Boot, Spring Data JPA, Lombok, and MySQL
What you need
JDK 8+ or OpenJDK 8+
Maven 3+
MySQL Server 5+
Your favorite IDE
Init project structure
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 Mapping would be implemented in Person.java and 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 shared primary key relationship. One person has only one id card, and one id card only belongs to one person
idcard.persion_id
is a 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;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "person")
private IDCard idCard;
public Person(String name, IDCard idCard) {
this.name = name;
this.idCard = idCard;
this.idCard.setPerson(this);
}
}
[IDCard.java]
package com.hellokoding.jpa.model;
import lombok.Data;
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 id;
@OneToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "person_id")
@MapsId
private Person person;
@Column(unique = true, nullable = false)
private String code = UUID.randomUUID().toString();
}
@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
@JoinColumn defines a foreign key column
@MapsId maps to the parent entity primary key
mappedBy value points to the relationship owner
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>{
}
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
Thanks to CascadeType.ALL
, associated entity IDCard
will be saved at the same time with Person
without the need of calling its save function explicitly
[Application.java]
package com.hellokoding.jpa;
import com.hellokoding.jpa.model.IDCard;
import com.hellokoding.jpa.model.Person;
import com.hellokoding.jpa.repository.PersonRepository;
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.ArrayList;
import java.util.List;
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
private PersonRepository personRepository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... strings) throws Exception {
// save idCard along with persons
List<Person> persons = new ArrayList<>();
persons.add(new Person("Tom", new IDCard()));
persons.add(new Person("Daisy", new IDCard()));
persons.add(new Person("Alex", new IDCard()));
personRepository.saveAll(persons);
}
}
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