In this tutorial, we will learn how to implement step-by-step many-to-many entity mapping using JPA, and Hibernate with MySQL database.
Learn hibernate at https://www.javaguides.net/p/hibernate-tutorial.html.
In many-to-many associations, 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.
Overview
Consider the following tables where employees and projects exhibit a many-to-many relationship with 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.
Check out below related hibernate association articles:JPA/Hibernate One to One Mapping Annotation Example
JPA/Hibernate One to One Bidirectional Mapping Annotation Example
JPA Hibernate One to Many Unidirectional Mapping Example
JPA/Hibernate One to Many Bidirectional Mapping Example
Let’s now create a simple maven project from scratch and learn how to go about implementing such a many-to-many relationship using JPA and Hibernate.
Tools and Technologies used
- Hibernate 6+
- JDK 17+
- Maven 3+
- IDE - STS or Eclipse
- MySQL 8+
1. Create a Simple Maven Project
Use the How to Create a Simple Maven Project in Eclipse article to create a simple Maven project in Eclipse IDE.
2. Project Directory Structure
Let's create a packaging structure for the above-created simple maven project.
3. POM Dependencies
<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>
<parent>
<groupId>net.javaguides.hibernate</groupId>
<artifactId>hibernate-tutorial</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>hibernate-many-to-many-example</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>8.1.7.Final</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
4. Creating the JPA Entities(Persistent classes)
Let's create JPA entities that we map with database tables. We use @ManyToMany annotation to create a many-to-many relationship between two entities.
In a bi-directional association, the @ManyToMany annotation is used on both entities but only one entity can be the owner of the relationship.
Employee
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.*;
@Entity
@Table(name = "employees")
public class Employee {
private static final long serialVersionUID = 1L;
@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
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.*;
@Entity
@Table(name = "projects")
public class Project {
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;
}
}
Understanding JPA annotations used
Let's understand the JPA annotations used in the above Employee and Project JPA entities:
- @Entity - This annotation specifies that the class is an entity.
- @Table - This annotation specifies the table in the database with which this entity is mapped.
- @Id - This annotation specifies the primary key of the entity.
- @GeneratedValue - This annotation specifies the generation strategies for the values of primary keys.
- @Column - The @Column annotation is used to specify the mapping between a basic entity attribute and the database table column.
- @ManyToMany - The @ManyToMany annotation is used to specify a many-to-many database relationship.
- @JoinColumn - The @JoinColumn annotation is used to specify the FOREIGN KEY column used when joining an entity association or an embeddable collection.
- @JoinColumns - The @JoinColumns annotation is used to group multiple @JoinColumn annotations, which are used when mapping entity association or an embeddable collection using a composite identifier.
- @JoinTable - The @JoinTable annotation is used to specify the link table between two other database tables.
5. Create a Hibernate configuration file - hibernate.cfg.xml
The configuration file contains information about the database and mapping file. Conventionally, its name should be hibernate.cfg.xml.
Let's create an XML file named hibernate.cfg.xml under the resources folder and write the following code in it.
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- JDBC Database connection settings --> <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/java_demo?useSSL=false</property> <property name="connection.username">root</property> <property name="connection.password">root</property> <!-- Echo the SQL to stdout --> <property name="show_sql">true</property> <!-- Set the current session context --> <property name="current_session_context_class">thread</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">create-drop</property> <mapping class="net.javaguides.hibernate.entity.Employee"/> <mapping class="net.javaguides.hibernate.entity.Project"/> </session-factory> </hibernate-configuration>
6. Create a Hibernate Utility Class
Create a helper class to bootstrap hibernate SessionFactory. In most Hibernate applications, the SessionFactory should be instantiated once during application initialization. The single instance should then be used by all code in a particular process, and any Session should be created using this single SessionFactory.
The SessionFactory is thread-safe and can be shared; a Session is a single-threaded object. Let's create a HibernateUtil class to configure singleton SessionFactory and use it throughout the application.
The SessionFactory is thread-safe and can be shared; a Session is a single-threaded object. Let's create a HibernateUtil class to configure singleton SessionFactory and use it throughout the application.
The bootstrapping API is quite flexible, but in most cases, it makes the most sense to think of it as a 3 step process:
- Build the StandardServiceRegistry
- Build the Metadata
- Use those 2 to build the SessionFactory
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
public class HibernateUtil {
private static StandardServiceRegistry registry;
private static SessionFactory sessionFactory;
public static SessionFactory getSessionFactory() {
if (sessionFactory == null) {
try {
// Create registry
registry = new StandardServiceRegistryBuilder().configure().build();
// Create MetadataSources
MetadataSources sources = new MetadataSources(registry);
// Create Metadata
Metadata metadata = sources.getMetadataBuilder().build();
// Create SessionFactory
sessionFactory = metadata.getSessionFactoryBuilder().build();
} catch (Exception e) {
e.printStackTrace();
if (registry != null) {
StandardServiceRegistryBuilder.destroy(registry);
}
}
}
return sessionFactory;
}
public static void shutdown() {
if (registry != null) {
StandardServiceRegistryBuilder.destroy(registry);
}
}
}
7. Test Application
package net.javaguides.hibernate.test; import org.hibernate.Session; import net.javaguides.hibernate.entity.Employee; import net.javaguides.hibernate.entity.Project; import net.javaguides.hibernate.util.HibernateUtil; public class Test { public static void main(String[] args) { Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); // 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); session.persist(employee); session.getTransaction().commit(); HibernateUtil.shutdown(); } }
Output: The hibernate will generate below SQL statements for insert queries:
Hibernate: insert into employees (first_name, last_name) values (?, ?)
Hibernate: insert into projects (title, project_id) values (?, ?)
Hibernate: insert into projects (title, project_id) values (?, ?)
Hibernate: insert into employees_projects (employee_id, project_id) values (?, ?)
Hibernate: insert into employees_projects (employee_id, project_id) values (?, ?)
8. Output
Below a screenshot showing the successfully ran this application with valid output:
Comments
Post a Comment
Leave Comment