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:

one to one on primary key

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:

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:

one to one on primary key



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 Producclass 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:

one to one with foreign key

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:

one to one with join table

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:


About the Author:

is certified Java programmer (SCJP and SCWCD). He started programming with Java in the time of Java 1.4 and has been falling in love with Java since then. Make friend with him on Facebook and watch his Java videos you YouTube.



Attachments:
Download this file (SpringDataJPAOne2One.zip)SpringDataJPAOne2One.zip[Sample Spring Boot projects]245 kB

Add comment