Many-to-Many Unidirectional Mapping in JPA

This topic teaches us how to implement many-to-many unidirectional 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 unidirectional mapping to generate a third table which contains foreign keys of tables. This mapping navigates in one direction(Student to Subject). The diagram representation of this mapping is shown below.

many-to-many_unidirectional_mapping_in_jpa

We will create a restful web service example to perform many-to-many unidirectional 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 Unidirectional Mapping

many-to-many_unidirectional_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.
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 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") })
private List<Subject> subList;
}

→ We used @ManyToMany annotation in this class to make many-to-many 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.
Subject.java

package com.springjava.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Data;
@Data
@Entity
public class Subject {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}

→ This @Data annotation is used to generate this class’s constructor, setter method, getter method, etc.

5. Creating Spring Data JPA Repositories

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 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.
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 unidirectional mapping Right-click on the DemoApplication.java then click on Run As and select Java Application.
To test the API on the Postman
Url: http://localhost:8080/api/student/save

many-to-many_unidirectional_mapping_in_jpa

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

many-to-many_unidirectional_mapping_in_jpa

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

many-to-many_unidirectional_mapping_in_jpa

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

many-to-many_unidirectional_mapping_in_jpa

See both tables below here:

many-to-many_unidirectional_mapping_in_jpa
many_to_many_unidirectional_mapping_in_jpa
many-to-many_unidirectional_mapping_in_jpa

9. Conclusion

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

Leave a Comment