avatarVinotech

Summary

The provided content is a comprehensive guide to implementing a complete CRUD application for managing Student and Course modules in Spring Boot with MySQL, including DTO validation, a common API response, and testing with Postman.

Abstract

This guide outlines a step-by-step approach to creating a full-fledged CRUD application using Spring Boot, MySQL, and other technologies. It covers setting up the project with necessary dependencies, configuring the MySQL database, defining entity and DTO classes for Student and Course modules with validation, implementing a common API response structure, creating repositories and service layer methods for CRUD operations, and testing the endpoints with Postman. The article emphasizes the use of DTOs for data transfer, validation annotations for input validation, and a generic API response class for consistent API output. It also includes error handling through a custom ResourceNotFoundException. The guide aims to provide a practical example for developers to understand and implement CRUD operations with best practices in a Spring Boot application.

Opinions

  • The author suggests that using DTOs (Data Transfer Objects) is a best practice for data transfer between processes.
  • Validation is highlighted as a critical aspect of API development to ensure data integrity and prevent malformed data from being processed.
  • A common API response structure is recommended for consistency and ease of client-side handling of API responses.
  • The use of Lombok library is endorsed for reducing boilerplate code in entity and DTO classes.
  • The guide promotes the practice of separating concerns by using distinct layers such as repository, service, and controller in the application architecture.
  • Testing with Postman is encouraged as a method for verifying the functionality of the API endpoints.
  • The author values the importance of proper error handling and demonstrates its implementation through a custom exception class.
  • The article implies that understanding and implementing CRUD operations with Spring Boot is essential for developers working with Spring framework.
  • The author invites readers to engage with the content by suggesting that if the articles are found useful, they should be shared and applauded, indicating a desire for community feedback and recognition.

Complete CRUD Example in Spring Boot with DTO Validation, and Common API Response using MySQL

Here’s a complete CRUD example for the Student and Course modules using Spring Boot, MySQL, a one-to-many relationship, DTO validation, a common API Response, and Postman testing.

spring-boot-student-course-crud/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── studentcourse/
│   │   │               ├── controller/
│   │   │               │   └── StudentController.java
│   │   │               ├── dto/
│   │   │               │   ├── StudentDTO.java
│   │   │               │   ├── CourseDTO.java
│   │   │               ├── entity/
│   │   │               │   ├── Student.java
│   │   │               │   ├── Course.java
│   │   │               ├── exception/
│   │   │               │   ├── ResourceNotFoundException.java
│   │   │               ├── repository/
│   │   │               │   ├── StudentRepository.java
│   │   │               │   ├── CourseRepository.java
│   │   │               ├── service/
│   │   │               │   └── StudentService.java
│   │   │               ├── response/
│   │   │               │   └── ApiResponse.java
│   │   │               └── SpringBootStudentCourseCrudApplication.java
│   │   ├── resources/
│   │   │   ├── application.properties
│   │   │   └── data.sql
│   ├── test/
│   │   └── java/
│   │       └── com/
│   │           └── example/
│   │               └── studentcourse/
│   │                   └── SpringBootStudentCourseCrudApplicationTests.java
├── .gitignore
├── mvnw
├── mvnw.cmd
├── pom.xml
└── README.md

Step 1: Project Setup

  • Dependencies: Spring Web, Spring Data JPA, MySQL Driver, Lombok, Spring Boot Validation, and Spring DevTools.

pom.xml:

<dependencies>
    <!-- Spring Boot Dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- Validation -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

Step 2: MySQL Database Configuration

In application.properties or application.yml:

spring.datasource.url=jdbc:mysql://localhost:3306/school_db
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Step 3: Entity Classes

Student.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Name is mandatory")
    @Size(min = 3, max = 50, message = "Name should be between 3 to 50 characters")
    private String name;

    @Email(message = "Email should be valid")
    private String email;

    @OneToMany(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Course> courses = new ArrayList<>();
}

Course.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Course name is mandatory")
    private String courseName;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "student_id")
    private Student student;
}

Step 4: DTO Classes

StudentDTO.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentDTO {
    private Long id;

    @NotBlank(message = "Name is mandatory")
    private String name;

    @Email(message = "Email should be valid")
    private String email;

    private List<CourseDTO> courses;
}

CourseDTO.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CourseDTO {
    private Long id;

    @NotBlank(message = "Course name is mandatory")
    private String courseName;
}

Step 5: Common API Response

ApiResponse.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
    private boolean success;
    private String message;
    private T data;
}

Step 6: Repositories

StudentRepository.java

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}

CourseRepository.java

@Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
}

Step 7: Service Layer

StudentService.java

@Service
public class StudentService {

    @Autowired
    private StudentRepository studentRepository;

    @Autowired
    private CourseRepository courseRepository;

    // Create Student
    public ApiResponse<StudentDTO> createStudent(StudentDTO studentDTO) {
        Student student = new Student();
        student.setName(studentDTO.getName());
        student.setEmail(studentDTO.getEmail());

        List<Course> courses = studentDTO.getCourses().stream().map(courseDTO -> {
            Course course = new Course();
            course.setCourseName(courseDTO.getCourseName());
            course.setStudent(student);
            return course;
        }).collect(Collectors.toList());

        student.setCourses(courses);
        studentRepository.save(student);

        return new ApiResponse<>(true, "Student created successfully", studentDTO);
    }

    // Get All Students
    public ApiResponse<List<StudentDTO>> getAllStudents() {
        List<StudentDTO> students = studentRepository.findAll().stream().map(student -> {
            StudentDTO studentDTO = new StudentDTO();
            studentDTO.setId(student.getId());
            studentDTO.setName(student.getName());
            studentDTO.setEmail(student.getEmail());
            studentDTO.setCourses(student.getCourses().stream().map(course -> {
                CourseDTO courseDTO = new CourseDTO();
                courseDTO.setId(course.getId());
                courseDTO.setCourseName(course.getCourseName());
                return courseDTO;
            }).collect(Collectors.toList()));
            return studentDTO;
        }).collect(Collectors.toList());

        return new ApiResponse<>(true, "List of all students", students);
    }

    // Get Student by ID
    public ApiResponse<StudentDTO> getStudentById(Long id) {
        Student student = studentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Student not found with id: " + id));

        StudentDTO studentDTO = new StudentDTO();
        studentDTO.setId(student.getId());
        studentDTO.setName(student.getName());
        studentDTO.setEmail(student.getEmail());
        studentDTO.setCourses(student.getCourses().stream().map(course -> {
            CourseDTO courseDTO = new CourseDTO();
            courseDTO.setId(course.getId());
            courseDTO.setCourseName(course.getCourseName());
            return courseDTO;
        }).collect(Collectors.toList()));

        return new ApiResponse<>(true, "Student found", studentDTO);
    }

    // Update Student
    public ApiResponse<StudentDTO> updateStudent(Long id, StudentDTO studentDTO) {
        Student student = studentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Student not found with id: " + id));

        student.setName(studentDTO.getName());
        student.setEmail(studentDTO.getEmail());

        student.getCourses().clear();
        List<Course> courses = studentDTO.getCourses().stream().map(courseDTO -> {
            Course course = new Course();
            course.setCourseName(courseDTO.getCourseName());
            course.setStudent(student);
            return course;
        }).collect(Collectors.toList());

        student.setCourses(courses);
        studentRepository.save(student);

        return new ApiResponse<>(true, "Student updated successfully", studentDTO);
    }

    // Delete Student
    public ApiResponse<Void> deleteStudent(Long id) {
        Student student = studentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Student not found with id: " + id));

        studentRepository.delete(student);

        return new ApiResponse<>(true, "Student deleted successfully", null);
    }
}

ResourceNotFoundException.java

package com.example.studentcourse.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException(String message) {
        super(message);
    }
}

Step 8: Controller Layer

StudentController.java

@RestController
@RequestMapping("/api/students")
public class StudentController {

    @Autowired
    private StudentService studentService;

    // Create Student
    @PostMapping
    public ResponseEntity<ApiResponse<StudentDTO>> createStudent(@Valid @RequestBody StudentDTO studentDTO) {
        ApiResponse<StudentDTO> response = studentService.createStudent(studentDTO);
        return new ResponseEntity<>(response, HttpStatus.CREATED);
    }

    // Get All Students
    @GetMapping
    public ResponseEntity<ApiResponse<List<StudentDTO>>> getAllStudents() {
        ApiResponse<List<StudentDTO>> response = studentService.getAllStudents();
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    // Get Student by ID
    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse<StudentDTO>> getStudentById(@PathVariable Long id) {
        ApiResponse<StudentDTO> response = studentService.getStudentById(id);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    // Update Student
    @PutMapping("/{id}")
    public ResponseEntity<ApiResponse<StudentDTO>> updateStudent(@PathVariable Long id, @Valid @RequestBody StudentDTO studentDTO) {
        ApiResponse<StudentDTO> response = studentService.updateStudent(id, studentDTO);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    // Delete Student
    @DeleteMapping("/{id}")
    public ResponseEntity<ApiResponse<Void>> deleteStudent(@PathVariable Long id) {
        ApiResponse<Void> response = studentService.deleteStudent(id);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }
}

Step 9: Testing with Postman

  1. POST /api/students
  • Body:
{
  "name": "John Doe",
  "email": "[email protected]",
  "courses": [
    {"courseName": "Mathematics"},
    {"courseName": "Physics"}
  ]
}

Response:

{
  "success": true,
  "message": "Student created successfully",
  "data": {
    "name": "John Doe",
    "email": "[email protected]",
    "courses": [
      {"courseName": "Mathematics"},
      {"courseName": "Physics"}
    ]
  }
}

GET /api/students

  • Response:
[
  {
    "id": 1,
    "name": "John Doe",
    "email": "[email protected]",
    "courses": [
      {"id": 1, "courseName": "Mathematics"},
      {"id": 2, "courseName": "Physics"}
    ]
  }
]

Update Student

Endpoint: PUT /api/students/{id}

  • Method: PUT
  • URL: http://localhost:8080/api/students/1 (Assuming 1 is the student ID you want to update)
  • Request Body (JSON):
{
  "name": "John Updated",
  "email": "[email protected]",
  "courses": [
    {
      "courseName": "Updated Mathematics"
    },
    {
      "courseName": "Updated Physics"
    }
  ]
}

Response:

{
  "success": true,
  "message": "Student updated successfully",
  "data": {
    "id": 1,
    "name": "John Updated",
    "email": "[email protected]",
    "courses": [
      {
        "id": 1,
        "courseName": "Updated Mathematics"
      },
      {
        "id": 2,
        "courseName": "Updated Physics"
      }
    ]
  }
}

2. Delete Student

Endpoint: DELETE /api/students/{id}

  • Method: DELETE
  • URL: http://localhost:8080/api/students/1 (Assuming 1 is the student ID you want to delete)
  • Response:
{
  "success": true,
  "message": "Student deleted successfully",
  "data": null
}

Step 10: Conclusion

This example demonstrates how to create a complete CRUD application with Student and Course modules, using a one-to-many relationship, DTO validation, and a common API response structure. You can expand this with update and delete functionality similarly.

👏 If you found my articles useful, please consider giving it claps and sharing it with your friends and colleagues.

To read other topics

Crud Spring Boot
Spring Boot Rest Api
Spring Boot
Spring Boot Project
Jpa
Recommended from ReadMedium