In this tutorial, we will learn how to perform one-to-many domain model mapping using Spring Data JPA (Hibernate as JPA provider).
Check out one-to-many bidirectional mapping tutorial at Spring Data JPA One to Many Bidirectional Mapping
The unidirectional One-To-Many association is simpler since it’s just the parent-side that defines the relationship. In unidirectional mapping, we use only @OneToMany annotation.
Check out my Udemy course to Learn Spring Data JPA: Master Spring Data JPA with Hibernate
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.
1. Creating Spring Boot Project
Spring Boot provides a web tool called https://start.spring.io to bootstrap an application quickly. Just go to https://start.spring.io and generate a new spring boot project.Use the below details in the Spring boot creation:
Project Name: spring-data-jpa-course
Project Type: Maven
Choose dependencies: Spring Data JPA, MySQL Driver, Lombok
Package name: net.javaguides.springboot
Use the below details in the Spring boot creation:
Project Name: spring-data-jpa-course
Project Type: Maven
Choose dependencies: Spring Data JPA, MySQL Driver, Lombok
Package name: net.javaguides.springboot
2. Maven Dependencies
<?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>
<?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>
3. Configure MySQL database
Let's use the MySQL database to store and retrieve the data in this example and we gonna use Hibernate properties to create and drop tables.
Open the application.properties file and add the following configuration to it: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.MySQLDialect
spring.jpa.hibernate.ddl-auto = create-drop
Make sure that you will create a demo database before running the Spring boot application.Also, change the MySQL username and password as per your MySQL installation on your machine.
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.MySQLDialect
spring.jpa.hibernate.ddl-auto = create-drop
4. Create JPA Entities
Let's create an entity package inside a base package "net.javaguides.springboot".
Within the entity package, create Order and OrderItems classes with the following content:
Order.java
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;
// one to many unidirectional mapping
// default fetch type for OneToMany: LAZY
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "order_id", referencedColumnName = "id")
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 +
'}';
}
}
OrderItems.java
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;
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;
}
}
5. Create Spring Data JPA Repositories
The next thing we’re gonna do is to create repositories to access the Order entity 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 an OrderRepository interface with the following content:
The next thing we’re gonna do is to create repositories to access the Order entity 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.
OrderRepository.java
package net.javaguides.springdatajpacourse.repository;
import net.javaguides.springdatajpacourse.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long>{
Order findByOrderTrackingNumber(String orderTrackingNumber);
}
6. Testing One to Many Unidirectional Mapping
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);
}
}
Save Order will also save associated order items:
@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);
}
Update Order Operation
@Test
void testUpdateOrder(){
Order order = orderRepository.findById(1L).get();
order.setStatus("DELIVERED");
orderRepository.save(order);
}
Get All Orders will also get its associated order items:
@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());
}));
});
}
Delete Order will also delete its associated order items:
@Test
void testDeleteOrder(){
orderRepository.deleteById(1L);
}
Comments
Post a Comment
Leave Comment