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 toupdateautomatically 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/studentmanagementdirectory, create a new package namedmodel.
- In the
-
Create the
StudentClass:- Inside the
modelpackage, create a new class namedStudent. - Add the following code to the
Studentclass:
- 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
CourseClass:- Inside the
modelpackage, create a new class namedCourse. - Add the following code to the
Courseclass:
- 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.IDENTITYindicates that the primary key is auto-incremented.@ManyToMany: Specifies a many-to-many relationship between theStudentandCourseentities.@JoinTable: Specifies the join table for the many-to-many relationship, including the join columns for both entities.- Utility Methods:
addCourseandremoveCourseare 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/studentmanagementdirectory, create a new package namedrepository.
- In the
-
Create the
StudentRepositoryInterface:- Inside the
repositorypackage, create a new interface namedStudentRepository. - Add the following code to the
StudentRepositoryinterface:
- 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: TheStudentRepositoryinterface extendsJpaRepository, providing CRUD operations for theStudententity. TheJpaRepositoryinterface includes methods likesave(),findById(),findAll(),deleteById(), etc.- Generics: The
JpaRepositoryinterface takes two parameters: the entity type (Student) and the type of its primary key (Long).
Create Course Repository
Create the CourseRepository Interface
- Create the
CourseRepositoryInterface:- Inside the
repositorypackage, 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: TheCourseRepositoryinterface extendsJpaRepository, providing CRUD operations for theCourseentity. TheJpaRepositoryinterface includes methods likesave(),findById(),findAll(),deleteById(), etc.- Generics: The
JpaRepositoryinterface 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/studentmanagementdirectory, create a new package namedservice.
- In the
-
Create the
StudentServiceInterface:- Inside the
servicepackage, create a new interface namedStudentService. - Add the following code to the
StudentServiceinterface:
- 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
StudentServiceImplClass:- Inside the
servicepackage, create a new class namedStudentServiceImpl. - Add the following code to the
StudentServiceImplclass:
- 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: TheStudentRepositoryinstance is injected into the service class to interact with the database.- Exception Handling: The
getStudentById,updateStudent, anddeleteStudentmethods throw aRuntimeExceptionif the student is not found.
Create CourseService Interface
- Create the
CourseServiceInterface:- Inside the
servicepackage, create a new interface namedCourseService. - Add the following code to the
CourseServiceinterface:
- 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
CourseServiceImplClass:- Inside the
servicepackage, create a new class namedCourseServiceImpl. - Add the following code to the
CourseServiceImplclass:
- 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: TheCourseRepositoryinstance is injected into the service class to interact with the database.- Exception Handling: The
getCourseById,updateCourse, anddeleteCoursemethods throw aRuntimeExceptionif the course is not found.
Create StudentController
Create the StudentController Class
-
Create a New Package:
- In the
src/main/java/com/example/studentmanagementdirectory, create a new package namedcontroller.
- In the
-
Create the
StudentControllerClass:- Inside the
controllerpackage, create a new class namedStudentController. - Add the following code to the
StudentControllerclass:
- 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/studentsto 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
CourseControllerClass:- Inside the
controllerpackage, create a new class namedCourseController. - Add the following code to the
CourseControllerclass:
- 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/coursesto 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
StudentManagementApplicationclass.
- In IntelliJ IDEA, run the
Access the Application
-
Open Web Browser:
- Open a web browser and go to
http://localhost:8080/api/studentsto manage students. - Open a web browser and go to
http://localhost:8080/api/coursesto 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.