Spring Data JPA One to One Mapping Examples
- Details
- Written by Nam Ha Minh
- Last Updated on 07 December 2022   |   Print Email
In this Spring Data JPA article, I’d happy to share with you some code examples of mapping one to one entity relationship in relational database, with Hibernate as the underlying ORM framework and Spring Boot as the application framework.
Why One to One Entity Relationship?
In relational database design, we use one-to-one association when we want to segment data that would be in a single table, into 2 separate ones, for the ease of data retrieval and processing. Consider the following example:
Here, a row in the products table is linked with only one row in the product_details table, and vice versa. In this article, you’ll learn how to implement different kinds of one-to-one mapping with Spring Data JPA:
- Mapping one-to-one relationship with shared primary key
- Mapping one-to-one relationship with foreign key
- Mapping one-to-one relationship with join table
Let’s see the code examples with description below.
1. Spring Data JPA One to One with Shared Primary Key
Suppose that we need to implement the following one to one relationship between tables products and product_details:
This is called one to one with shared primary key because the both tables sharing the same values of in their primary keys, in which the product_id column of the product_details table is also a foreign key referring the id column of the products table.
So in Java code, we write the Product class that maps with the products table as follows:
package net.codejava; import javax.persistence.*; @Entity @Table(name = "products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private float price; @OneToOne(mappedBy = "product", cascade = CascadeType.ALL) @PrimaryKeyJoinColumn private ProductDetail detail; public Product() { } public Product(String name, float price) { super(); this.name = name; this.price = price; } // getters and setters... }
Note that the @OneToOne and @PrimaryKeyJoinColumn annotations are used for the field detail of type ProductDetail.
And code the ProductDetail class that maps with the product_details table as follows:
package net.codejava; import javax.persistence.*; @Entity @Table(name = "product_details") public class ProductDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "product_id") private Integer id; private String description; private float weight; private float length; private float height; private float width; @OneToOne @JoinColumn(name = "product_id") @MapsId private Product product; // getters and setters... }
Note that the field product of type Product is annotated with @OneToOne, @JoinColumn and @MapsId annotations.
And to make use of Spring Data JPA, create the following repository interface:
package net.codejava; import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository<Product, Integer> { }
And below is a sample unit test class that tests persisting a new Product object and listing all products:
package net.codejava; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.annotation.Rollback; @DataJpaTest @AutoConfigureTestDatabase(replace = Replace.NONE) @Rollback(false) public class ProductRepositoryTests { @Autowired private ProductRepository repo; @Test public void testListAll() { List<Product> products = repo.findAll(); assertThat(products).isNotEmpty(); products.forEach(System.out::println); } @Test public void testAdd() { Product product = new Product("iPhone 15", 999); ProductDetail detail = new ProductDetail(); detail.setProduct(product); product.setDetail(detail); detail.setDescription("New iPhone version in 2023"); detail.setWeight(2.5f); detail.setHeight(0.7f); detail.setLength(4.0f); detail.setWidth(3.5f); Product savedProduct = repo.save(product); assertThat(savedProduct).isNotNull(); } }
You see, the code is self-explanatory. Note that in this one-to-one with shared primary key entity relationship, a product must have details (mandatory).
2. Spring Data JPA One to One with Foreign Key
The second kind of one-to-one relationship is with foreign key on the owner side. Suppose that we need to map the following two tables with Spring Data JPA:
Here, the products table has a foreign key column detail_id that refers to the primary key column id of the product_details table.
To implement this kind of one to one relationship, code the Product class as below:
package net.codejava; import javax.persistence.*; @Entity @Table(name = "products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private float price; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "detail_id", referencedColumnName = "id") private ProductDetail detail; public Product() { } public Product(String name, float price) { super(); this.name = name; this.price = price; } // getters and setters... }
And code the ProductDetail class as below:
package net.codejava; import javax.persistence.*; @Entity @Table(name = "product_details") public class ProductDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String description; private float weight; private float length; private float height; private float width; // getters and setters }
As you can see, there’s no reference to Product in the ProductDetail class, thus the relationship is unidirectional.
To make use of Spring Data JPA, code the repository interface as usual:
package net.codejava; import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository<Product, Integer> { }
For your reference, below is an example unit test class:
package net.codejava; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.annotation.Rollback; @DataJpaTest @AutoConfigureTestDatabase(replace = Replace.NONE) @Rollback(false) public class ProductRepositoryTests { @Autowired private ProductRepository repo; @Test public void testListAll() { List<Product> products = repo.findAll(); assertThat(products).isNotEmpty(); products.forEach(System.out::println); } @Test public void testAdd() { Product product = new Product("iPhone 15", 999); ProductDetail detail = new ProductDetail(); product.setDetail(detail); detail.setDescription("New iPhone version in 2023"); detail.setWeight(2.5f); detail.setHeight(0.7f); detail.setLength(4.0f); detail.setWidth(3.5f); Product savedProduct = repo.save(product); assertThat(savedProduct).isNotNull(); } }
Also note that, it’s mandatory for a product to have details.
3. Spring Data JPA One to One with Join Table
The third kind of one-to-one entity relationship is with join table, which should be used if it’s not mandatory. Consider the following example:
In this one to one relationship between products and details, a product can have details or not, so we need the join table product_details in order to avoid null values if a product doesn’t have details.
In Java code, we write the Product entity class as follows:
package net.codejava; import javax.persistence.*; @Entity @Table(name = "products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private float price; @OneToOne(cascade = CascadeType.ALL) @JoinTable(name = "products_details", joinColumns = {@JoinColumn(name = "product_id", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "detail_id", referencedColumnName = "id")} ) private ProductDetail detail; public Product() { } public Product(String name, float price) { super(); this.name = name; this.price = price; } // getters and setters }
Here, we use the @JoinTable annotation to specify the join table details. And code the ProductDetail entity class as below:
package net.codejava; import javax.persistence.*; @Entity @Table(name = "details") public class ProductDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String description; private float weight; private float length; private float height; private float width; @OneToOne(mappedBy = "detail") private Product product; // getters and setters... }
And for your reference, below is an example unit test class that tests persist a Product object without and with details:
package net.codejava; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.annotation.Rollback; @DataJpaTest @AutoConfigureTestDatabase(replace = Replace.NONE) @Rollback(false) public class ProductRepositoryTests { @Autowired private ProductRepository repo; @Test public void testListAll() { List<Product> products = repo.findAll(); assertThat(products).isNotEmpty(); products.forEach(System.out::println); } @Test public void testAddProductWithoutDetail() { Product product = new Product("iPhone 15", 999); Product savedProduct = repo.save(product); assertThat(savedProduct).isNotNull(); } @Test public void testAddProductWithDetail() { Product product = new Product("Google Pixel 5", 289); ProductDetail detail = new ProductDetail(); product.setDetail(detail); detail.setDescription("New Android Phone in 2023"); detail.setWeight(3.5f); detail.setHeight(0.5f); detail.setLength(4.5f); detail.setWidth(4.1f); Product savedProduct = repo.save(product); assertThat(savedProduct).isNotNull(); } }
The code is self-explanatory, and I hope you understand the purpose of using join table in one to one entity relationship. You can download the sample project code under the Attachments section below.
To see the coding in action, watch my video below:
Related Spring and Database Tutorials:
- Spring Data JPA EntityManager Examples (CRUD Operations)
- JPA EntityManager: Understand Differences between Persist and Merge
- Understand Spring Data JPA with Simple Example
- Spring Data JPA Custom Repository Example
- Spring MVC with JdbcTemplate Example
- How to configure Spring MVC JdbcTemplate with JNDI Data Source in Tomcat
- Spring and Hibernate Integration Tutorial (XML Configuration)
- Spring MVC + Spring Data JPA + Hibernate - CRUD Example
Comments