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
问题原因与修复方案
核心原因
异常未触发的大概率原因有两个:
- 邮箱查询逻辑未覆盖大小写场景:PostgreSQL默认对字符串查询区分大小写,若数据库中存在
maria@gmail.com,但请求传入Maria@gmail.com,查询会返回空,无法触发重复校验。 - 数据库层无邮箱唯一约束:仅依赖代码校验存在漏判风险,若代码逻辑出现疏漏,会导致数据库出现重复邮箱数据。
修复步骤
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
相关产品推荐
相关产品推荐

