In this tutorial, we will learn how to implement step by step many-to-many entity mapping using JPA/Hibernate with Spring Boot, Spring Data JPA, and MySQL database.
In many-to-many association, the source entity has a field that stores a collection of target entities. The @ManyToMany JPA annotation is used to link the source entity with the target entity.
A many-to-many association always uses an intermediate join table to store the association that joins two entities. The join table is defined using the @JoinTable JPA annotation.
Video
Overview
Consider the following tables where employees and projects exhibit a many-to-many relationship between each other -
The many-to-many relationship is implemented using a third table called employees_projects which contains the details of the employees and their associated projects. Note that here Employee is a primary entity.
Let’s now create a Spring boot project from scratch and learn how to go about implementing such a many-to-many relationship using JPA and Hibernate.
Tools and Technologies used
- Spring boot 2+
- Hibernate 5+
- JDK 1.8+
- Maven 3+
- IDE - STS or Eclipse
- Spring Data JPA
- MySQL 5+
Development Steps
- Create a Spring Boot Application
- Maven pom Dependencies
- Project Structure
- Configuring the Database and Hibernate Log levels
- Defining the Domain Models
- Defining the Repositories
- Run and Test the Application
1. Create a Spring Boot Application
There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.
>> Create Spring Boot Project With Spring Initializer
>> Create Spring Boot Project in Spring Tool Suite [STS]
>> Create Spring Boot Project in Spring Tool Suite [STS]
Refer to project structure or packaging structure from step 3.
2. Maven Dependencies
Let's add required maven dependencies to pom.xml:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.guides.springboot2</groupId>
<artifactId>springboot-jpa-many-to-many-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. Project Structure
Let's refer below screenshot to create our Project packaging structure -
4. Configuring the Database and Hibernate Log levels
We’ll need to configure MySQL database URL, username, and password so that Spring can establish a connection with the database on startup.
Open src/main/resources/application.properties and add the following properties to it -
logging.pattern.console=%clr(%d{yy-MM-dd E HH:mm:ss.SSS}){blue} %clr(%-5p) %clr(%logger{0}){blue} %clr(%m){faint}%n
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
5. Defining the Domain Models
We’ll abstract out these common fields in a separate class called AuditModel and extend this class in the Employee and Project entities.
AuditModel
package net.guides.springboot.jparepository.model;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditModel implements Serializable {
private static final long serialVersionUID = 1 L;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at", nullable = false, updatable = false)
@CreatedDate
private Date createdAt;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "updated_at", nullable = false)
@LastModifiedDate
private Date updatedAt;
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
}
In the above class, we’re using Spring Boot’s AuditingEntityListener to automatically populate the createdAt and updatedAt fields.
Enabling JPA Auditing
To enable JPA Auditing, we need to add @EnableJpaAuditing annotation to one of our configuration classes. Let's open the main class Application.java and add the @EnableJpaAuditing to the main class like so -
@SpringBootApplication
@EnableJpaAuditing // Enabling JPA Auditing
public class Application implements CommandLineRunner {
// code goes here
}
Employee
package net.guides.springboot.jparepository.model;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name = "employees")
public class Employee extends AuditModel {
private static final long serialVersionUID = 1 L;
@Id
@Column(name = "employee_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long employeeId;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@ManyToMany(cascade = {
CascadeType.ALL
})
@JoinTable(
name = "employees_projects",
joinColumns = {
@JoinColumn(name = "employee_id")
},
inverseJoinColumns = {
@JoinColumn(name = "project_id")
}
)
Set < Project > projects = new HashSet < Project > ();
public Employee() {
super();
}
public Employee(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Employee(String firstName, String lastName, Set < Project > projects) {
this.firstName = firstName;
this.lastName = lastName;
this.projects = projects;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Set < Project > getProjects() {
return projects;
}
public void setProjects(Set < Project > projects) {
this.projects = projects;
}
}
Project
package net.guides.springboot.jparepository.model;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name = "projects")
public class Project extends AuditModel {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "project_id")
@GeneratedValue
private Long projectId;
@Column(name = "title")
private String title;
@ManyToMany(mappedBy = "projects", cascade = { CascadeType.ALL })
private Set<Employee> employees = new HashSet<Employee>();
public Project() {
super();
}
public Project(String title) {
this.title = title;
}
public Long getProjectId() {
return projectId;
}
public void setProjectId(Long projectId) {
this.projectId = projectId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Set<Employee> getEmployees() {
return employees;
}
public void setEmployees(Set<Employee> employees) {
this.employees = employees;
}
}
The entity that specifies the @JoinTable is the owning side of the relationship and the entity that specifies the mappedBy attribute is the inverse side.
6. Defining the Repositories
Let’s define the repositories for accessing the Employee and Project data from the database.
EmployeeRepository
package net.guides.springboot.jparepository.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import net.guides.springboot.jparepository.model.Employee;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>{
}
ProjectRepository
package net.guides.springboot.jparepository.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import net.guides.springboot.jparepository.model.Project;
@Repository
public interface ProjectRepository extends JpaRepository<Project, Long>{
}
7. Run and Test Many-to-Many relationship
Time to write some code to test our many-to-many association setup. Open the main class Application.java and replace it with the following code. Let's run this Application.main() method:
package net.guides.springboot.jparepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import net.guides.springboot.jparepository.model.Employee; import net.guides.springboot.jparepository.model.Project; import net.guides.springboot.jparepository.repository.EmployeeRepository; @SpringBootApplication @EnableJpaAuditing // Enabling JPA Auditing public class Application implements CommandLineRunner { @Autowired private EmployeeRepository employeeRepository; @Override public void run(String...args) throws Exception { // Create an employee Employee employee = new Employee(); employee.setFirstName("Ramesh"); employee.setLastName("Fadatare"); // Create project1 Project project = new Project(); project.setTitle("Employee Management System"); // Create project2 Project project1 = new Project(); project1.setTitle("Content Management System"); // employee can work on two projects, Add project references in the employee employee.getProjects().add(project); employee.getProjects().add(project1); // Add employee reference in the projects project.getEmployees().add(employee); project1.getEmployees().add(employee); employeeRepository.save(employee); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Output
Conclusion
You might also be interested in checking out the following articles on JPA and Hibernate -
Hello, my name is Robson and I am developing a Web project and among Crud's forms there is the Master Detail, I am using Spring Boot 2, Thymeleaf, Bootstrap 4 .... javascript, jquery and I looked for some tutorial that explained how to develop a Crud Master Detail I haven't found, could you tell me something?
ReplyDelete