JPA and Hibernate Many to Many Mapping with Spring Boot - @ManyToMany and @JoinTable

In this tutorial, we will learn how to implement step by step many-to-many entity mapping using JPA/Hibernate with Spring BootSpring Data JPA, and MySQL database.
Learn Spring boot at https://www.javaguides.net/p/spring-boot-tutorial.html.
Learn Hibernate at https://www.javaguides.net/p/hibernate-tutorial.html 

Video Tutorial

 This tutorial explained in below youtube video:

Overview

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.
Consider the following tables where posts and tags exhibit a many-to-many relationship between each other:
The many-to-many relationship is implemented using a third table called post_tags which contains the details of posts and their associated tags.
Let’s now create a spring boot project from scratch and learn how to go about implementing such many-to-many relationships 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

  1. Create a Spring Boot Application
  2. Maven pom Dependencies
  3. Project Structure
  4. Configuring the Database and Hibernate Log levels
  5. Defining the Domain Models
  6. Defining the Repositories
  7. 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.

2. Maven pom 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 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>2.2.6.RELEASE</version>
        <relativePath/>
        <!-- lookup parent from repository -->
    </parent>
    <groupId>net.javaguides</groupId>
    <artifactId>springboot-hibernate-many-to-many-mapping</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-hibernate-many-to-many-mapping</name>
    <description>Demo project for Spring Boot Hibernate many to many mapping</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </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 URLusername, 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:
spring.datasource.url = jdbc:mysql://localhost:3306/demo?useSSL=false
spring.datasource.username = root
spring.datasource.password = root


## Hibernate Properties
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 -

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

logging.level.org.hibernate.SQL=DEBUG
Make sure that you change the spring.datasource.username and spring.datasource.password properties as per your MySQL installation. Also, create a database named demo.
The spring.jpa.hibernate.ddl-auto = update property makes sure that the database tables and the domain models in your application are in sync. Whenever you change the domain model, hibernate will automatically update the mapped table in the database when you restart the application.
I have also specified the log levels for hibernate so that we can debug the SQL queries executed by hibernate.

5. Defining the Domain Models

Let’s define the domain models which will be mapped to the tables we saw earlier.
Let's create Post and Tag domain models.

Post.java

package net.javaguides.springboot.entity;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
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;

/**
 * Post domain model
 * @author javaguides.net
 *
 */
@Entity
@Table(name = "posts")
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "title")
    private String title;

    @Column(name = "description")
    private String description;

    @Column(name = "content")
    private String content;

    @Column(name = "posted_at")
    private Date postedAt = new Date();

    @Column(name = "last_updated_at")
    private Date lastUpdatedAt = new Date();

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = "post_tags",
        joinColumns = {
            @JoinColumn(name = "post_id")
        },
        inverseJoinColumns = {
            @JoinColumn(name = "tag_id")
        })
    private Set < Tag > tags = new HashSet < > ();

    public Post() {

    }

    public Post(String title, String description, String content) {
        super();
        this.title = title;
        this.description = description;
        this.content = content;
    }

    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public Date getPostedAt() {
        return postedAt;
    }
    public void setPostedAt(Date postedAt) {
        this.postedAt = postedAt;
    }
    public Date getLastUpdatedAt() {
        return lastUpdatedAt;
    }
    public void setLastUpdatedAt(Date lastUpdatedAt) {
        this.lastUpdatedAt = lastUpdatedAt;
    }
    public Set < Tag > getTags() {
        return tags;
    }
    public void setTags(Set < Tag > tags) {
        this.tags = tags;
    }
}

Tag.java

package net.javaguides.springboot.entity;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

/**
 * Tag domain model
 * @author javaguides.net
 *
 */
@Entity
@Table(name = "tags")
public class Tag {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "tags")
    private Set < Post > posts = new HashSet < > ();

    public Tag() {

    }

    public Tag(String name) {
        super();
        this.name = name;
    }
    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 Set < Post > getPosts() {
        return posts;
    }
    public void setPosts(Set < Post > posts) {
        this.posts = posts;
    }
}
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 the entities but only one entity can be the owner of the relationship.
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.
In the above example, the Post entity is the owner of the relationship, and the Tag entity is the inverse side.

6. Defining the Repositories

Let’s define the repositories for accessing the Post and Tag data from the database.

PostRepository.java

package net.javaguides.springboot.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import net.javaguides.springboot.entity.Post;

@Repository
public interface PostRepository extends JpaRepository<Post, Long>{

}

TagRepository.java

package net.javaguides.springboot.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import net.javaguides.springboot.entity.Tag;

@Repository
public interface TagRepository extends JpaRepository<Tag, Long>{

}

7. Run and Test the Application

Let's test our many-to-many association setup. This is our main entry point of the Spring boot project. To run spring boot project, just right-click on this file and run as Java application:
package net.javaguides.springboot;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import net.javaguides.springboot.entity.Post;
import net.javaguides.springboot.entity.Tag;
import net.javaguides.springboot.repository.PostRepository;

@SpringBootApplication
public class SpringbootHibernateManyToManyMappingApplication implements CommandLineRunner {

    @Autowired
    private PostRepository postRepository;

    public static void main(String[] args) {
        SpringApplication.run(SpringbootHibernateManyToManyMappingApplication.class, args);
    }

    @Override
    public void run(String...args) throws Exception {

        Post post = new Post("Hibernate Many to Many Mapping Example with Spring Boot",
            "Hibernate Many to Many Mapping Example with Spring Boot",
            "Hibernate Many to Many Mapping Example with Spring Boot");

        Post post1 = new Post("Hibernate One to Many Mapping Example with Spring Boot",
            "Hibernate One to Many Mapping Example with Spring Boot",
            "Hibernate One to Many Mapping Example with Spring Boot");

        Tag springBoot = new Tag("Spring Boot");
        Tag hibernate = new Tag("Hibernate");

        // add tag references post
        post.getTags().add(springBoot);
        post.getTags().add(hibernate);

        // add post references tag

        springBoot.getPosts().add(post);
        hibernate.getPosts().add(post);

        springBoot.getPosts().add(post1);
        post1.getTags().add(springBoot);


        this.postRepository.save(post);
        this.postRepository.save(post1);
    }
}

Output:

Once the application starts, check the console for hibernate specific logs to see what SQL statements are executed by hibernate for the operations that we have performed in the run() method:

Spring Boot Hibernate Mapping

Comments

  1. Hi, Ramesh, May I know how to print those values in post_tags table on thymeleaf?
    I tried a lot of method available but unable to get the result

    ReplyDelete

Post a comment