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
- Introduction
- Create and Setup Spring Boot Project in IntelliJ IDEA
- Configure H2 Database
- Create Student and Course Entities
- Configure Many-to-Many Relationship
- Create Student Repository
- Create Course Repository
- Create Service Layer
- StudentService
- StudentServiceImpl
- CourseService
- CourseServiceImpl
- Create StudentController
- Create CourseController
- Test the Application
- Conclusion
Create and Setup Spring Boot Project in IntelliJ IDEA
Create a New Spring Boot Project
-
Open Spring Initializr:
- Go to Spring Initializr in your web browser.
-
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)
-
Add Dependencies:
- Spring Web
- Spring Data JPA
- H2 Database
-
Generate the Project:
- Click "Generate" to download the project as a ZIP file.
-
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
-
Open
application.properties
:- Navigate to
src/main/resources/application.properties
.
- Navigate to
-
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 toupdate
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
-
Create a New Package:
- In the
src/main/java/com/example/studentmanagement
directory, create a new package namedmodel
.
- In the
-
Create the
Student
Class:- Inside the
model
package, create a new class namedStudent
. - Add the following code to the
Student
class:
- Inside the
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
- Create the
Course
Class:- Inside the
model
package, create a new class namedCourse
. - Add the following code to the
Course
class:
- Inside the
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 theStudent
andCourse
entities.@JoinTable
: Specifies the join table for the many-to-many relationship, including the join columns for both entities.- Utility Methods:
addCourse
andremoveCourse
are utility methods to manage the many-to-many relationship from both sides.
Create Student Repository
Create the StudentRepository
Interface
-
Create a New Package:
- In the
src/main/java/com/example/studentmanagement
directory, create a new package namedrepository
.
- In the
-
Create the
StudentRepository
Interface:- Inside the
repository
package, create a new interface namedStudentRepository
. - Add the following code to the
StudentRepository
interface:
- Inside the
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
: TheStudentRepository
interface extendsJpaRepository
, providing CRUD operations for theStudent
entity. TheJpaRepository
interface includes methods likesave()
,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
- Create the
CourseRepository
Interface:- Inside the
repository
package, create a new interface namedCourseRepository
. - Add the following code to the
CourseRepository
- Inside the
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
: TheCourseRepository
interface extendsJpaRepository
, providing CRUD operations for theCourse
entity. TheJpaRepository
interface includes methods likesave()
,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
-
Create a New Package:
- In the
src/main/java/com/example/studentmanagement
directory, create a new package namedservice
.
- In the
-
Create the
StudentService
Interface:- Inside the
service
package, create a new interface namedStudentService
. - Add the following code to the
StudentService
interface:
- Inside the
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
- Create the
StudentServiceImpl
Class:- Inside the
service
package, create a new class namedStudentServiceImpl
. - Add the following code to the
StudentServiceImpl
class:
- Inside the
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
: TheStudentRepository
instance is injected into the service class to interact with the database.- Exception Handling: The
getStudentById
,updateStudent
, anddeleteStudent
methods throw aRuntimeException
if the student is not found.
Create CourseService Interface
- Create the
CourseService
Interface:- Inside the
service
package, create a new interface namedCourseService
. - Add the following code to the
CourseService
interface:
- Inside the
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
- Create the
CourseServiceImpl
Class:- Inside the
service
package, create a new class namedCourseServiceImpl
. - Add the following code to the
CourseServiceImpl
class:
- Inside the
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
: TheCourseRepository
instance is injected into the service class to interact with the database.- Exception Handling: The
getCourseById
,updateCourse
, anddeleteCourse
methods throw aRuntimeException
if the course is not found.
Create StudentController
Create the StudentController
Class
-
Create a New Package:
- In the
src/main/java/com/example/studentmanagement
directory, create a new package namedcontroller
.
- In the
-
Create the
StudentController
Class:- Inside the
controller
package, create a new class namedStudentController
. - Add the following code to the
StudentController
class:
- Inside the
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
- Create the
CourseController
Class:- Inside the
controller
package, create a new class namedCourseController
. - Add the following code to the
CourseController
class:
- Inside the
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
- Run the Application:
- In IntelliJ IDEA, run the
StudentManagementApplication
class.
- In IntelliJ IDEA, run the
Access the Application
-
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.
- Open a web browser and go to
-
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.