You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot学生管理PUT接口邮箱重复校验异常失效求助

Spring Boot学生管理系统PUT更新接口异常排查与修复

问题现象

基于Spring Boot开发的学生管理系统CRUD功能整体正常,但PUT更新接口存在异常:

  • 使用有效ID更新学生姓名可正常生效
  • 更新不存在的ID时能正确抛出异常
  • 更新学生信息时使用已被其他学生占用的邮箱,未触发预期的「Email already taken」异常,仍返回200状态码

相关代码

StudentController类

package com.example.demo.student;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping(path = "api/v1/student")
public class StudentController {
    private final StudentService studentService;

    @Autowired
    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }

    @GetMapping
    public List<Student> getStudents(){
        return studentService.getStudents();
    }

    @PostMapping
    public void registerNewStudent(@RequestBody Student student){
        studentService.addNewStudent(student);
    }

    @DeleteMapping(path = "{studentId}")
    public void deleteStudent(@PathVariable ("studentId") Long studentId){
        studentService.deleteStudent(studentId);
    }

    @PutMapping(path = "{studentId}")
    public void updateStudent(
            @PathVariable("studentId") Long studentId,
            @RequestParam(required = false) String name,
            @RequestParam(required = false) String email){
        studentService.updateStudent(studentId, name, email);
    }
}

StudentService类

package com.example.demo.student;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Service
public class StudentService {
    private final StudentRepository studentRepository;

    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public List<Student> getStudents(){
        return studentRepository.findAll();
    }

    public void addNewStudent(Student student) {
        Optional<Student> studentOptional = studentRepository
                .findStudentByEmail(student.getEmail());
        if(studentOptional.isPresent()){
            throw new IllegalStateException("Email already taken");
        }
        studentRepository.save(student);
    }

    public void deleteStudent(Long studentId) {
        boolean exists = studentRepository.existsById(studentId);
        if(!exists){
            throw new IllegalStateException(
                    "Student with ID " + studentId + " does not exists.");
        }
        studentRepository.deleteById(studentId);
    }

    @Transactional
    public void updateStudent( Long studentId,
                               String name,
                               String email){
        Student student = studentRepository.findById(studentId)
                .orElseThrow(() -> new IllegalStateException(
                        "Student with ID " + studentId + " does not exists"));

        if(name != null && name.length() > 0 &&
            !Objects.equals(student.getName(),name)){
            student.setName(name);
        }
        if(email != null && email.length() > 0 &&
                !Objects.equals(student.getEmail(),email)){
            Optional<Student> studentOptional = studentRepository.findStudentByEmail(email);
            if(studentOptional.isPresent()){
                throw new IllegalStateException("Email already taken");
            }
            student.setEmail(email);
        }
    }
}

测试请求示例

###
PUT http://localhost:8080/api/v1/student/1?name=Maria&email=maria@gmail.com
Content-Type: application/json

<> 2023-04-14T205328.500.json

应用配置(application.properties)

spring.datasource.url=jdbc:postgresql://localhost:5432/student
spring.datasource.username= postgres
spring.datasource.password= 9435561259
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true

server.error.include-message=always

问题原因与修复方案

核心原因

异常未触发的大概率原因有两个:

  1. 邮箱查询逻辑未覆盖大小写场景:PostgreSQL默认对字符串查询区分大小写,若数据库中存在maria@gmail.com,但请求传入Maria@gmail.com,查询会返回空,无法触发重复校验。
  2. 数据库层无邮箱唯一约束:仅依赖代码校验存在漏判风险,若代码逻辑出现疏漏,会导致数据库出现重复邮箱数据。

修复步骤

1. 给Student实体邮箱字段添加唯一约束

在Student实体类的邮箱字段上添加数据库层面的唯一约束,避免脏数据产生:

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    // 添加唯一约束,同时禁止为空
    @Column(unique = true, nullable = false)
    private String email;
    // 其他字段、构造方法、Getter/Setter
}

2. 优化邮箱校验逻辑,支持大小写不敏感查询

修改StudentRepository,添加大小写不敏感的查询方法:

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
    // 原方法保留,新增大小写不敏感查询
    Optional<Student> findStudentByEmailIgnoreCase(String email);
}

同步更新StudentService中的updateStudent和addNewStudent方法,使用新的查询方法:

// updateStudent方法中修改
Optional<Student> studentOptional = studentRepository.findStudentByEmailIgnoreCase(email);

// addNewStudent方法中同步修改
Optional<Student> studentOptional = studentRepository.findStudentByEmailIgnoreCase(student.getEmail());

3. 自定义异常处理,返回规范状态码(可选)

创建全局异常处理器,将重复邮箱的异常映射为更符合REST规范的409 Conflict状态码:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<String> handleIllegalStateException(IllegalStateException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.CONFLICT);
    }
}

验证步骤

修复后重启应用,测试以下场景:

  • 用其他学生已使用的邮箱更新,确认返回「Email already taken」异常,状态码为409
  • 用当前学生自身邮箱更新,确认不触发异常,返回200
  • 更新不存在的ID,确认抛出「Student with ID X does not exists」异常
  • 更新有效ID的姓名,确认正常生效

内容的提问来源于stack exchange,提问作者Swarup Saha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 19:27:17