Many-to-Many Bidirectional Mapping in JPA

This topic teaches us how to implement many-to-many bidirectional mapping between two JPA entities using Spring Boot, Spring Data JPA, H2 database and Lombok. We will take Student and Subject entities to implement many-to-many bidirectional mapping to generate a third table which contains foreign keys of tables. This mapping navigates in both directions (Student to Subject and vice-versa). We will use @ManyToMany annotations in these two entity classes to make bidirectional mapping. The diagram representation of this kind of mapping is shown below.

many-to-many_bidirectional_mapping_in_jpa

We will create a restful web service example to perform many-to-many bidirectional mapping using the Spring Boot application step-by-step. Let’s begin to implement

1. Creating a Spring Boot Starter Project

We are creating a Spring Boot Application from the web tool Spring Initializr.
Add the following dependencies: 
   • Spring Web 
   • Lombok
   • H2 Database
   • Spring Data JPA

Project Structure of Many-to-Many Bidirectional Mapping

many-to-many_bidirectional_mapping_in_jpa

2. Maven Dependency

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.6.3</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springjava</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>    
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3. Define the H2 database configuration

We are configuring the H2 database configuration in the application.properties file.

spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true

4. Creating JPA Entities

We are creating two JPA entity classes Student and Subject to make many-to-many bidirectional mapping.
Student.java

package com.springjava.entity;
import java.util.List;
import javax.persistence.CascadeType;
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 com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

@Data
@Entity
public class Student{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String email;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "student_subject", 
joinColumns = { @JoinColumn(name = "stu_id") }, 
inverseJoinColumns = { @JoinColumn(name = "sub_id") })
@JsonIgnoreProperties("stuList")
private List<Subject> subList;
}

→ We used @ManyToMany annotation in this class to make many-to-many unidirectional mapping with the Subject entity class.
→ We used @JoinTable annotation to generate a third table with the mentioned name which contains foreign keys with mentioned names.
→ This @JsonIgnoreProperties annotation is used to mark the property that Jackson ignored.
Subject.java

package com.springjava.entity;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

@Data
@Entity
public class Subject  {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ManyToMany(mappedBy = "subList")
@JsonIgnoreProperties("subList")
private List<Student> stuList;
}

→ This @Data annotation is used to generate a constructor, setter method, getter method, etc. for this class.
→ We used @ManyToMany annotation in this class to make many-to-many bidirectional mapping.

5. Creating Spring Data JPA Repositories

We are creating two JPA repositories for the persistence operations of the created JPA entity classes.
StudentRepository.java

package com.springjava.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.springjava.entity.Student;
public interface StudentRepository extends JpaRepository<Student, Integer> {
}

SubjectRepository.java

package com.springjava.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.springjava.entity.Subject;
public interface SubjectRepository extends JpaRepository<Subject, Integer> {
}

6. Creating Service Interfaces and Classes

We are creating one interface StudentService and a class implementation  StudentServiceImpl of that service interface.
StudentService.java

package com.springjava.service;
import java.util.List;
import com.springjava.entity.Student;
public interface StudentService {
void save(Student student);
List<Student> findAll();
}

StudentServiceImpl.java

package com.springjava.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springjava.entity.Student;
import com.springjava.repository.StudentRepository;

@Service
public class StudentServiceImpl implements StudentService {
@Autowired
StudentRepository stuRepo;

@Override
public void save(Student student) {
stuRepo.save(student);
}
@Override
public List<Student> findAll() {
return stuRepo.findAll();
    }
}

We are creating one interface SubjectService and a class implementation class SubjectServiceImpl of that service interface.
SubjectService.java

package com.springjava.service;
import java.util.List;
import com.springjava.entity.Subject;
public interface SubjectService {
List<Subject> findAll();
}

SubjectServiceImpl.java

package com.springjava.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springjava.entity.Subject;
import com.springjava.repository.SubjectRepository;

@Service
public class SubjectServiceImpl implements SubjectService {
@Autowired
SubjectRepository subRepo;

@Override
public List<Subject> findAll() {
return subRepo.findAll();
    }
}

7. Creating Data Model class

We are creating a data model class which is a Java Bean class for passing request body parameters in the controller class.

StudentModel.java

package com.springjava.model;
import java.util.List;
import com.springjava.entity.Subject;
import lombok.Data;

@Data
public class StudentModel {
private String name;
private String email;
private List<Subject>subjects;
}

8. Creating Controller classes

We are creating two controller classes StudentController and SubjectController which create API mapping of the application.
StudentController.java

package com.springjava.controller;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springjava.entity.Student;
import com.springjava.model.StudentModel;
import com.springjava.service.StudentService;

@RestController
@RequestMapping("/api")
public class StudentController {
    @Autowired
    StudentService stuService;

    @PostMapping("/student/save")
    public ResponseEntity<?> save(@RequestBody StudentModel student) {
        Map<String, Object> respStu = new LinkedHashMap<String, Object>();
        Student stu = new Student();
        stu.setName(student.getName());
        stu.setEmail(student.getEmail());
        stu.setSubList(student.getSubjects());
        stuService.save(stu);
        respStu.put("status", 1);
        respStu.put("message", "Record is Saved Successfully!");
        return new ResponseEntity<>(respStu, HttpStatus.CREATED);
    }

    @GetMapping("/student/list")
    public ResponseEntity<?> getStudents() {
        Map<String, Object> respStu = new LinkedHashMap<String, Object>();
        List<Student> studList = stuService.findAll();
        if (!studList.isEmpty()) {
            respStu.put("status", 1);
            respStu.put("data", studList);
            return new ResponseEntity<>(respStu, HttpStatus.OK);
        } else {
            respStu.clear();
            respStu.put("status", 0);
            respStu.put("message", "Data is not found");
            return new ResponseEntity<>(respStu, HttpStatus.NOT_FOUND);
        }
    }
}

SubjectController.java

package com.springjava.controller;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springjava.entity.Subject;
import com.springjava.service.SubjectService;

@RestController
@RequestMapping("/api")
public class SubjectController {
   @Autowired
   SubjectService subService;
    
  @GetMapping("/subject/list")
  public ResponseEntity<?> getSubjects() {
            Map<String, Object> respSub = new LinkedHashMap<String, Object>();
            List<Subject> subList=subService.findAll();
            if (!subList.isEmpty()) {    
                respSub.put("status", 1);
                respSub.put("data", subList);
                return new ResponseEntity<>(respSub, HttpStatus.OK);
            } else {
                respSub.clear();
                respSub.put("status", 0);
                respSub.put("message", "Data is not found");
                return new ResponseEntity<>(respSub, HttpStatus.NOT_FOUND);
            }
        }
}

9. Run the Spring Boot Application

To run this application many-to-many bidirectional mapping Right-click on the DemoApplication.java, then click Run As and select Java Application.
To test the API on the Postman
Url: http://localhost:8080/api/student/save

many-to-many_bidirectional_mapping_in_jpa

Url: http://localhost:8080/api/student/list

many-to-many_bidirectional_mapping_in_jpa

Url: http://localhost:8080/api/subject/list

many-to-many_bidirectional_mapping_in_jpa

To check the H2 database, we can browse this URL “http://localhost:8080/h2-console” on the browser to view the database tables created by this spring boot application(many-to-many bidirectional mapping).

many-to-many_bidirectional_mapping_in_jpa

See both tables below here:

many-to-many_bidirectional_mapping_in_jpa
many-to-many_bidirectional_mapping_in_jpa
many-to-many_bidirectional_mapping_in_jpa

9. Conclusion

In this topic, we learnt how to implement many-to-many bidirectional mapping in spring boot, spring data JPA, Lombok and H2 database with rest API example.

Leave a Comment