Automatically Saving Child Entities in JPA
In JPA applications, we often encounter entities with parent-child relationships. Persisting these entities efficiently involves saving the parent and automatically persisting its associated child objects. This article explores how to achieve this automatic saving for both unidirectional and bidirectional relationships.
1. Understanding JPA Relationships
In JPA, relationships between entities are managed using annotations. There are several types of relationships:
- One-to-One: A single instance of an entity is associated with a single instance of another entity.
- One-to-Many: A single instance of an entity is associated with multiple instances of another entity.
- Many-to-One: Multiple instances of an entity are associated with a single instance of another entity.
- Many-to-Many: Multiple instances of an entity are associated with multiple instances of another entity.
For this article, we will focus on the One-to-Many and Many-to-One relationships.
1.1 Understanding CascadeType
The key to automatic child object persistence lies in the CascadeType
property of JPA annotations (@OneToMany
and @OneToOne
). This property defines how persistence operations (persist, update, delete) on the parent entity cascade to its child entities.
Here are the main CascadeType
options:
CascadeType.PERSIST
: Saves child objects along with the parent.CascadeType.MERGE
: Updates existing child objects along with the parent.CascadeType.REMOVE
: Deletes child objects along with the parent.CascadeType.ALL
: Combines all the above behaviours.
2. Unidirectional Relationships
In a unidirectional relationship, the parent entity holds a collection of child entities, but the child entity has no reference back to the parent. Here is an example:
Parent Entity
Orders.java
@Entity public class Orders { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; String customername; String Status; @OneToMany(cascade = CascadeType.PERSIST) private List<OrderItem> items = new ArrayList<OrderItem>(); public Long getId() { return id; } public void setId(Long id) { this.id = id; } public List<OrderItem> getItems() { return items; } public void setItems(List<OrderItem> items) { this.items = items; } public String getCustomername() { return customername; } public void setCustomername(String customername) { this.customername = customername; } public String getStatus() { return Status; } public void setStatus(String Status) { this.Status = Status; } }
Child Entity
OrderItem.java
@Entity public class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String productname; private int quantity; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getProductname() { return productname; } public void setProductname(String productname) { this.productname = productname; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } }
In this example, the Orders
entity has a List
of OrderItem
objects annotated with @OneToMany
. The cascade
property is set to CascadeType.PERSIST
, ensuring that when an Order
is saved, it’s associated OrderItem
objects are also persisted automatically.
2.1 Saving Entities
Create a repository and a service to manage the saving of these entities.
2.1.1 Repository Interfaces
Parent Repository
OrderRepository.java
public interface OrderRepository extends JpaRepository<Orders, Long>{ }
Child Repository
OrderItemRepository.java
public interface OrderItemRepository extends JpaRepository<OrderItem, Long>{ }
2.1.2 Service Layer
Parent Service
OrderService.java
@Service public class OrderService { @Autowired private OrderRepository orderRepository; @Transactional public Orders saveParentWithChildren(Orders order) { return orderRepository.save(order); } }
Below is a simple test to verify our implementation. Here is how to save the parent:
TestDataLoader.java
@Component public class TestDataLoader implements CommandLineRunner { @Autowired private OrderService orderService; @Override public void run(String... args) throws Exception { Orders order = new Orders(); order.setStatus("New Status"); order.setCustomername("John Doe"); // Create OrderItem objects OrderItem item1 = new OrderItem(); item1.setProductname("Product A"); item1.setQuantity(2); OrderItem item2 = new OrderItem(); item2.setProductname("Product B"); item2.setQuantity(1); order.getItems().add(item1); order.getItems().add(item2); orderService.saveParentWithChildren(order); // Verify that the parent and children are saved correctly System.out.println("Parent and children saved successfully"); } }
JPA will persist the Order
and automatically issue the necessary SQL statements to persist the OrderItem
objects with the correct foreign key references to the Order
.
As shown in the log output below, Hibernate cascades the operation to the associated OrderItem
entities and persists them automatically.
3. Bidirectional Relationships
In a bidirectional relationship, both the parent and child entities hold references to each other. Here’s an example:
Parent Entity
Department.java
@Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy = "department", cascade = CascadeType.PERSIST) private List<Employee> employees = new ArrayList<>(); public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } }
Child Entity
Employee.java
@Entity public class Employee { @Id private Long id; private String name; @ManyToOne @JoinColumn(name = "department_id") private Department department; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Department getDepartment() { return department; } public void setDepartment(Department department) { this.department = department; } }
In this bidirectional example, the Child
(Employee) entity has a reference to the Parent
(Department) entity using the @ManyToOne
annotation. The Parent
entity’s @OneToMany
annotation now uses mappedBy
to specify the field in the Child
entity that owns the relationship.
The Department
entity has a List
of Employee
objects annotated with @OneToMany
. Additionally, the Employee
entity has a @ManyToOne
relationship with Department
. The cascade
property remains set to CascadeType.PERSIST
in the Department
entity.
3.1 Saving Entities
2.1.1 Repository Interfaces
Parent Repository
DepartmentRepository.java
public interface DepartmentRepository extends JpaRepository<Department, Long>{ }
Child Repository
EmployeeRepository.java
public interface EmployeeRepository extends JpaRepository<Employee, Long>{ }
2.1.2 Service Layer
Parent Service
DepartmentService.java
@Service public class DepartmentService { @Autowired private DepartmentRepository departmentRepository; @Transactional public Department saveParentWithChildren(Department department) { return departmentRepository.save(department); } }
Saving the parent in this scenario requires setting the bidirectional relationship before persisting:
TestDataLoader.java
@Component public class TestDataLoader implements CommandLineRunner { @Autowired private DepartmentService departmentService; @Override public void run(String... args) throws Exception { Department department = new Department(); department.setName("Engineering"); Employee emp1 = new Employee(); emp1.setName("John Doe"); emp1.setDepartment(department); Employee emp2 = new Employee(); emp2.setName("Jane Doe"); emp2.setDepartment(department); department.getEmployees().add(emp1); department.getEmployees().add(emp2); departmentService.saveParentWithChildren(department); } }
By setting the department
property in each Employee
object and adding them to the department.employees
list, we establish the bidirectional relationship. When department
is persisted, JPA cascades the PERSIST
operation to the Employee
objects, creating them in the database with the correct foreign key references.
Log output:
Hibernate: insert into Department (name, id) values (?, default) Hibernate: select next value for Employee_SEQ Hibernate: select next value for Employee_SEQ Hibernate: insert into Employee (department_id, name, id) values (?, ?, ?) Hibernate: insert into Employee (department_id, name, id) values (?, ?, ?)
4. Conclusion
In this article, we explored how to save parent entities along with their child entities automatically using JPA. We covered both unidirectional and bidirectional relationships, providing code examples. With these techniques, you can manage complex entity relationships more efficiently in your JPA-based applications.
5. Download the Source Code
This article is a guide on how to automatically save child objects using JPA.
You can download the full source code of this example here: JPA save child objects automatically