In this tutorial, we will learn how to perform one-to-many domain model Bidirectional mapping using Spring Data JPA ( Hibernate as JPA provider).
We will take Order and OrderItems entities to implement one-to-many Bidirectional mapping. The Order entity has a one-to-many association to the OrderItems entity, and the OrderItems entity has a many-to-one relationship to the Order entity.
In One to Many Bidirectional Mapping, we use JPA @OneToMany
and @ManyToOne
annotations for mapping with a
database table.
In a relational database system, a one-to-many association links two tables based on a Foreign Key column so
that the child table record references the Primary Key of the parent table row.
Spring Boot provides a web tool called https://start.spring.io to bootstrap an application quickly. Just go to the website and generate a new Spring Boot project using the details below:
Add the following dependencies to your pom.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>net.javaguides</groupId>
<artifactId>spring-data-jpa-course</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-jpa-course</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Use the following configurations in your application.properties
file:
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useSSL=false
spring.datasource.username=root
spring.datasource.password=Mysql@123
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto=create-drop
Ensure the demo database exists before running the application, and adjust the username and password according to your MySQL installation.
Let's create a entity
package inside a base package
"net.javaguides.springboot".
Within the entity
package, create Order and OrderItems
classes with the following content:
package net.javaguides.springdatajpacourse.entity;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name="orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Long id;
@Column(name="order_tracking_number")
private String orderTrackingNumber;
@Column(name="total_quantity")
private int totalQuantity;
@Column(name="total_price")
private BigDecimal totalPrice;
@Column(name="status")
private String status;
@Column(name="date_created")
@CreationTimestamp
private Date dateCreated;
@Column(name="last_updated")
@UpdateTimestamp
private Date lastUpdated;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "order")
private Set<OrderItem> orderItems = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOrderTrackingNumber() {
return orderTrackingNumber;
}
public void setOrderTrackingNumber(String orderTrackingNumber) {
this.orderTrackingNumber = orderTrackingNumber;
}
public int getTotalQuantity() {
return totalQuantity;
}
public void setTotalQuantity(int totalQuantity) {
this.totalQuantity = totalQuantity;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
public Date getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public Set<OrderItem> getOrderItems() {
return orderItems;
}
public void setOrderItems(Set<OrderItem> orderItems) {
this.orderItems = orderItems;
}
public void add(OrderItem item) {
if (item != null) {
if (orderItems == null) {
orderItems = new HashSet<>();
}
orderItems.add(item);
// item.setOrder(this);
}
}
public BigDecimal getTotalAmount()
{
BigDecimal amount = new BigDecimal("0.0");
for (OrderItem item : this.orderItems)
{
amount = amount.add(item.getPrice());
}
return amount;
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", orderTrackingNumber='" + orderTrackingNumber + '\'' +
", totalQuantity=" + totalQuantity +
", totalPrice=" + totalPrice +
", status='" + status + '\'' +
", dateCreated=" + dateCreated +
", lastUpdated=" + lastUpdated +
'}';
}
}
package net.javaguides.springdatajpacourse.entity;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name="order_items",schema = "ecommerce")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Long id;
@Column(name="image_url")
private String imageUrl;
@Column(name = "price")
private BigDecimal price;
@Column(name="quantity")
private int quantity;
@OneToOne
@JoinColumn(name="product_id")
private Product product;
// default fetch type for ManyToOne: EAGER
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public BigDecimal getPrice() {
return this.price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
}
The next thing we’re gonna do is to create repositories to access Order
and
Address
entities data from the
database.
The JpaRepository
interface defines methods for all the CRUD operations on
the entity, and a default
implementation of the JpaRepository
called SimpleJpaRepository
.
Let's create a repository
package inside a base package
"net.javaguides.springdatarest".
Within the repository
package, create OrderRepository
and AddressRepository
interfaces with the following
content:
package net.javaguides.springdatajpacourse.repository;
import net.javaguides.springdatajpacourse.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository{
Order findByOrderTrackingNumber(String orderTrackingNumber);
}
Let's write the JUnit test to perform CRUD operations on one-to-many unidirectional mapping using Spring Data JPA:
package net.javaguides.springdatajpacourse.repository;
import net.javaguides.springdatajpacourse.entity.Order;
import net.javaguides.springdatajpacourse.entity.OrderItem;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class OneToManyUnidirectionalMappingTest {
@Autowired
private OrderRepository orderRepository;
@Test
void testSaveOrder(){
// create Order object
Order order = new Order();
OrderItem orderItem = new OrderItem();
orderItem.setImageUrl("image_url.png");
orderItem.setPrice(new BigDecimal(100));
// add orderitem to order
order.add(orderItem);
OrderItem orderItem2 = new OrderItem();
orderItem2.setImageUrl("image_url.png");
orderItem2.setPrice(new BigDecimal(200));
// add orderItem2 to order
order.add(orderItem2);
order.setOrderTrackingNumber("1000");
order.setStatus("IN PROGRESS");
// total amount of the order
order.setTotalPrice(order.getTotalAmount());
// Quantity of the order items
order.setTotalQuantity(2);
orderRepository.save(order);
}
@Test
void testUpdateOrder(){
Order order = orderRepository.findById(1L).get();
order.setStatus("DELIVERED");
orderRepository.save(order);
}
@Test
void testGetAllOrders(){
List<Order> orders = orderRepository.findAll();
orders.forEach((o) -> {
System.out.println("order id :: " + o.getId());
o.getOrderItems().forEach((orderItem -> {
System.out.println("orderItem :: " + orderItem.getId());
}));
});
}
@Test
void testFindByOrderTrackingNumber(){
Order order = orderRepository.findByOrderTrackingNumber("1000");
// add fetch type as EAGER
// order.getOrderItems().forEach((o) -> {
// System.out.println(o.getId());
// });
}
@Test
void testDeleteOrder(){
orderRepository.deleteById(1L);
}
}
@Test
void testSaveOrder(){
// create Order object
Order order = new Order();
OrderItem orderItem = new OrderItem();
orderItem.setImageUrl("image_url.png");
orderItem.setPrice(new BigDecimal(100));
// add orderitem to order
order.add(orderItem);
OrderItem orderItem2 = new OrderItem();
orderItem2.setImageUrl("image_url.png");
orderItem2.setPrice(new BigDecimal(200));
// add orderItem2 to order
order.add(orderItem2);
order.setOrderTrackingNumber("1000");
order.setStatus("IN PROGRESS");
// total amount of the order
order.setTotalPrice(order.getTotalAmount());
// Quantity of the order items
order.setTotalQuantity(2);
orderRepository.save(order);
}
@Test
void testUpdateOrder(){
Order order = orderRepository.findById(1L).get();
order.setStatus("DELIVERED");
orderRepository.save(order);
}
@Test
void testGetAllOrders(){
List orders = orderRepository.findAll();
orders.forEach((o) -> {
System.out.println("order id :: " + o.getId());
o.getOrderItems().forEach((orderItem -> {
System.out.println("orderItem :: " + orderItem.getId());
}));
});
}
@Test
void testDeleteOrder(){
orderRepository.deleteById(1L);
}