Spring Boot Many-to-Many Mapping

Introduction

In this chapter, we will explore how to implement a many-to-many relationship in a Spring Boot application by building a Student Management Project. We will create entities for Student and Course, configure the many-to-many relationship between them, and demonstrate how to perform CRUD operations.

Table of Contents

  1. Introduction
  2. Create and Setup Spring Boot Project in IntelliJ IDEA
  3. Configure H2 Database
  4. Create Student and Course Entities
  5. Configure Many-to-Many Relationship
  6. Create Student Repository
  7. Create Course Repository
  8. Create Service Layer
    • StudentService
    • StudentServiceImpl
    • CourseService
    • CourseServiceImpl
  9. Create StudentController
  10. Create CourseController
  11. Test the Application
  12. Conclusion

Create and Setup Spring Boot Project in IntelliJ IDEA

Create a New Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: 3.2.0
    • Group: com.example
    • Artifact: student-management
    • Name: student-management
    • Description: Student Management System with Many-to-Many Mapping
    • Package name: com.example.studentmanagement
    • Packaging: Jar
    • Java: 17 (or the latest version available)
  3. Add Dependencies:

    • Spring Web
    • Spring Data JPA
    • H2 Database
  4. Generate the Project:

    • Click "Generate" to download the project as a ZIP file.
  5. Import Project into IntelliJ IDEA:

    • Open IntelliJ IDEA.
    • Click on "Open" and navigate to the downloaded ZIP file.
    • Extract the ZIP file and import the project.

Explanation

  • Spring Initializr: A web-based tool provided by Spring to bootstrap a new Spring Boot project with dependencies and configurations.
  • Group and Artifact: Define the project’s Maven coordinates.
  • Dependencies: Adding dependencies ensures that the necessary libraries are included in the project for web development, JPA, and H2 database connectivity.

Configure H2 Database

Update application.properties

  1. Open application.properties:

    • Navigate to src/main/resources/application.properties.
  2. Add H2 Database Configuration:

    • Add the following properties to configure the H2 database connection:
spring.datasource.url=jdbc:h2:mem:studentdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true spring.h2.console.path=/h2-console 

Explanation

  • spring.datasource.url: The JDBC URL to connect to the H2 database in memory.
  • spring.datasource.driverClassName: The driver class name for H2 database.
  • spring.datasource.username: The username to connect to the H2 database.
  • spring.datasource.password: The password to connect to the H2 database.
  • spring.jpa.hibernate.ddl-auto: Specifies the Hibernate DDL mode. Setting it to update automatically updates the database schema based on the entity mappings.
  • spring.h2.console.enabled: Enables the H2 database console for easy access to the database through a web browser.
  • spring.h2.console.path: Specifies the path to access the H2 console.

Create Student and Course Entities

Create the Student Class

  1. Create a New Package:

    • In the src/main/java/com/example/studentmanagement directory, create a new package named model.
  2. Create the Student Class:

    • Inside the model package, create a new class named Student.
    • Add the following code to the Student class:
package com.example.studentmanagement.model; import jakarta.persistence.*; import java.util.HashSet; import java.util.Set; @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; private String email; @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable( name = "student_course", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id") ) private Set<Course> courses = new HashSet<>(); // Constructors public Student() {} public Student(String firstName, String lastName, String email) { this.firstName = firstName; this.lastName = lastName; this.email = email; } // Getters and Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } 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 String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Set<Course> getCourses() { return courses; } public void setCourses(Set<Course> courses) { this.courses = courses; } public void addCourse(Course course) { this.courses.add(course); course.getStudents().add(this); } public void removeCourse(Course course) { this.courses.remove(course); course.getStudents().remove(this); } } 

Create the Course Class

  1. Create the Course Class:
    • Inside the model package, create a new class named Course.
    • Add the following code to the Course class:
package com.example.studentmanagement.model; import jakarta.persistence.*; import java.util.HashSet; import java.util.Set; @Entity public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String description; @ManyToMany(mappedBy = "courses", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private Set<Student> students = new HashSet<>(); // Constructors public Course() {} public Course(String name, String description) { this.name = name; this.description = description; } // Getters and Setters 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 String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } } 

Explanation

  • @Entity: Specifies that the class is an entity and is mapped to a database table.
  • @Id: Specifies the primary key of the entity.
  • @GeneratedValue: Specifies how the primary key should be generated. GenerationType.IDENTITY indicates that the primary key is auto-incremented.
  • @ManyToMany: Specifies a many-to-many relationship between the Student and Course entities.
  • @JoinTable: Specifies the join table for the many-to-many relationship, including the join columns for both entities.
  • Utility Methods: addCourse and removeCourse are utility methods to manage the many-to-many relationship from both sides.

Create Student Repository

Create the StudentRepository Interface

  1. Create a New Package:

    • In the src/main/java/com/example/studentmanagement directory, create a new package named repository.
  2. Create the StudentRepository Interface:

    • Inside the repository package, create a new interface named StudentRepository.
    • Add the following code to the StudentRepository interface:
package com.example.studentmanagement.repository; import com.example.studentmanagement.model.Student; import org.springframework.data.jpa.repository.JpaRepository; public interface StudentRepository extends JpaRepository<Student, Long> { } 

Explanation

  • JpaRepository: The StudentRepository interface extends JpaRepository, providing CRUD operations for the Student entity. The JpaRepository interface includes methods like save(), findById(), findAll(), deleteById(), etc.
  • Generics: The JpaRepository interface takes two parameters: the entity type (Student) and the type of its primary key (Long).

Create Course Repository

Create the CourseRepository Interface

  1. Create the CourseRepository Interface:
    • Inside the repository package, create a new interface named CourseRepository.
    • Add the following code to the CourseRepository

interface:

package com.example.studentmanagement.repository; import com.example.studentmanagement.model.Course; import org.springframework.data.jpa.repository.JpaRepository; public interface CourseRepository extends JpaRepository<Course, Long> { } 

Explanation

  • JpaRepository: The CourseRepository interface extends JpaRepository, providing CRUD operations for the Course entity. The JpaRepository interface includes methods like save(), findById(), findAll(), deleteById(), etc.
  • Generics: The JpaRepository interface takes two parameters: the entity type (Course) and the type of its primary key (Long).

Create Service Layer

Create StudentService Interface

  1. Create a New Package:

    • In the src/main/java/com/example/studentmanagement directory, create a new package named service.
  2. Create the StudentService Interface:

    • Inside the service package, create a new interface named StudentService.
    • Add the following code to the StudentService interface:
package com.example.studentmanagement.service; import com.example.studentmanagement.model.Student; import java.util.List; public interface StudentService { Student saveStudent(Student student); Student getStudentById(Long id); List<Student> getAllStudents(); Student updateStudent(Long id, Student studentDetails); void deleteStudent(Long id); } 

Explanation

  • Service Interface: Defines the contract for the service layer. It includes methods for saving, retrieving, updating, and deleting students.

Create StudentServiceImpl Class

  1. Create the StudentServiceImpl Class:
    • Inside the service package, create a new class named StudentServiceImpl.
    • Add the following code to the StudentServiceImpl class:
package com.example.studentmanagement.service; import com.example.studentmanagement.model.Student; import com.example.studentmanagement.repository.StudentRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class StudentServiceImpl implements StudentService { @Autowired private StudentRepository studentRepository; @Override public Student saveStudent(Student student) { return studentRepository.save(student); } @Override public Student getStudentById(Long id) { return studentRepository.findById(id) .orElseThrow(() -> new RuntimeException("Student not found with id: " + id)); } @Override public List<Student> getAllStudents() { return studentRepository.findAll(); } @Override public Student updateStudent(Long id, Student studentDetails) { Student student = studentRepository.findById(id) .orElseThrow(() -> new RuntimeException("Student not found with id: " + id)); student.setFirstName(studentDetails.getFirstName()); student.setLastName(studentDetails.getLastName()); student.setEmail(studentDetails.getEmail()); student.setCourses(studentDetails.getCourses()); return studentRepository.save(student); } @Override public void deleteStudent(Long id) { Student student = studentRepository.findById(id) .orElseThrow(() -> new RuntimeException("Student not found with id: " + id)); studentRepository.delete(student); } } 

Explanation

  • @Service: Indicates that this class is a service component in the Spring context.
  • StudentRepository: The StudentRepository instance is injected into the service class to interact with the database.
  • Exception Handling: The getStudentById, updateStudent, and deleteStudent methods throw a RuntimeException if the student is not found.

Create CourseService Interface

  1. Create the CourseService Interface:
    • Inside the service package, create a new interface named CourseService.
    • Add the following code to the CourseService interface:
package com.example.studentmanagement.service; import com.example.studentmanagement.model.Course; import java.util.List; public interface CourseService { Course saveCourse(Course course); Course getCourseById(Long id); List<Course> getAllCourses(); Course updateCourse(Long id, Course courseDetails); void deleteCourse(Long id); } 

Explanation

  • Service Interface: Defines the contract for the service layer. It includes methods for saving, retrieving, updating, and deleting courses.

Create CourseServiceImpl Class

  1. Create the CourseServiceImpl Class:
    • Inside the service package, create a new class named CourseServiceImpl.
    • Add the following code to the CourseServiceImpl class:
package com.example.studentmanagement.service; import com.example.studentmanagement.model.Course; import com.example.studentmanagement.repository.CourseRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class CourseServiceImpl implements CourseService { @Autowired private CourseRepository courseRepository; @Override public Course saveCourse(Course course) { return courseRepository.save(course); } @Override public Course getCourseById(Long id) { return courseRepository.findById(id) .orElseThrow(() -> new RuntimeException("Course not found with id: " + id)); } @Override public List<Course> getAllCourses() { return courseRepository.findAll(); } @Override public Course updateCourse(Long id, Course courseDetails) { Course course = courseRepository.findById(id) .orElseThrow(() -> new RuntimeException("Course not found with id: " + id)); course.setName(courseDetails.getName()); course.setDescription(courseDetails.getDescription()); course.setStudents(courseDetails.getStudents()); return courseRepository.save(course); } @Override public void deleteCourse(Long id) { Course course = courseRepository.findById(id) .orElseThrow(() -> new RuntimeException("Course not found with id: " + id)); courseRepository.delete(course); } } 

Explanation

  • @Service: Indicates that this class is a service component in the Spring context.
  • CourseRepository: The CourseRepository instance is injected into the service class to interact with the database.
  • Exception Handling: The getCourseById, updateCourse, and deleteCourse methods throw a RuntimeException if the course is not found.

Create StudentController

Create the StudentController Class

  1. Create a New Package:

    • In the src/main/java/com/example/studentmanagement directory, create a new package named controller.
  2. Create the StudentController Class:

    • Inside the controller package, create a new class named StudentController.
    • Add the following code to the StudentController class:
package com.example.studentmanagement.controller; import com.example.studentmanagement.model.Student; import com.example.studentmanagement.service.StudentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/students") public class StudentController { @Autowired private StudentService studentService; @PostMapping public ResponseEntity<Student> saveStudent(@RequestBody Student student) { Student savedStudent = studentService.saveStudent(student); return new ResponseEntity<>(savedStudent, HttpStatus.CREATED); } @GetMapping("/{id}") public ResponseEntity<Student> getStudentById(@PathVariable Long id) { Student student = studentService.getStudentById(id); return new ResponseEntity<>(student, HttpStatus.OK); } @GetMapping public ResponseEntity<List<Student>> getAllStudents() { List<Student> students = studentService.getAllStudents(); return new ResponseEntity<>(students, HttpStatus.OK); } @PutMapping("/{id}") public ResponseEntity<Student> updateStudent(@PathVariable Long id, @RequestBody Student studentDetails) { Student updatedStudent = studentService.updateStudent(id, studentDetails); return new ResponseEntity<>(updatedStudent, HttpStatus.OK); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteStudent(@PathVariable Long id) { studentService.deleteStudent(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } } 

Explanation

  • @RestController: Indicates that this class is a REST controller.
  • @RequestMapping("/api/students"): Maps HTTP requests to /api/students to methods in this controller.
  • @PostMapping: Handles HTTP POST requests to save a student.
  • @GetMapping("/{id}"): Handles HTTP GET requests to retrieve a student by ID.
  • @GetMapping: Handles HTTP GET requests to retrieve all students.
  • @PutMapping("/{id}"): Handles HTTP PUT requests to update a student’s details.
  • @DeleteMapping("/{id}"): Handles HTTP DELETE requests to delete a student by ID.

Create CourseController

Create the CourseController Class

  1. Create the CourseController Class:
    • Inside the controller package, create a new class named CourseController.
    • Add the following code to the CourseController class:
package com.example.studentmanagement.controller; import com.example.studentmanagement.model.Course; import com.example.studentmanagement.service.CourseService; import org.springframework.beans.factory.annotation.Autowired ; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/courses") public class CourseController { @Autowired private CourseService courseService; @PostMapping public ResponseEntity<Course> saveCourse(@RequestBody Course course) { Course savedCourse = courseService.saveCourse(course); return new ResponseEntity<>(savedCourse, HttpStatus.CREATED); } @GetMapping("/{id}") public ResponseEntity<Course> getCourseById(@PathVariable Long id) { Course course = courseService.getCourseById(id); return new ResponseEntity<>(course, HttpStatus.OK); } @GetMapping public ResponseEntity<List<Course>> getAllCourses() { List<Course> courses = courseService.getAllCourses(); return new ResponseEntity<>(courses, HttpStatus.OK); } @PutMapping("/{id}") public ResponseEntity<Course> updateCourse(@PathVariable Long id, @RequestBody Course courseDetails) { Course updatedCourse = courseService.updateCourse(id, courseDetails); return new ResponseEntity<>(updatedCourse, HttpStatus.OK); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteCourse(@PathVariable Long id) { courseService.deleteCourse(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } } 

Explanation

  • @RestController: Indicates that this class is a REST controller.
  • @RequestMapping("/api/courses"): Maps HTTP requests to /api/courses to methods in this controller.
  • @PostMapping: Handles HTTP POST requests to save a course.
  • @GetMapping("/{id}"): Handles HTTP GET requests to retrieve a course by ID.
  • @GetMapping: Handles HTTP GET requests to retrieve all courses.
  • @PutMapping("/{id}"): Handles HTTP PUT requests to update a course’s details.
  • @DeleteMapping("/{id}"): Handles HTTP DELETE requests to delete a course by ID.

Test the Application

Run the Application

  1. Run the Application:
    • In IntelliJ IDEA, run the StudentManagementApplication class.

Access the Application

  1. Open Web Browser:

    • Open a web browser and go to http://localhost:8080/api/students to manage students.
    • Open a web browser and go to http://localhost:8080/api/courses to manage courses.
  2. Verify the Application:

    • Verify that you can perform CRUD operations on students and courses and manage the many-to-many relationship between them.

Conclusion

In this chapter, we built a Student Management System project using Spring Boot with a many-to-many relationship between Student and Course entities. We configured the H2 database, created entities and repositories, and demonstrated how to perform CRUD operations through a RESTful API. Each step was explained in detail to help you understand how to implement many-to-many relationships in a Spring Boot application.

Leave a Comment

Scroll to Top