Hibernate Many to Many Annotation Mapping Example

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. 

Refer below screenshot for your reference.

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 bootstrapping API is quite flexible, but in most cases, it makes the most sense to think of it as a 3 step process:
  1. Build the StandardServiceRegistry
  2. Build the Metadata
  3. 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:

Related tutorials

Comments