CRUD应用中@ControllerAdvice与@ExceptionHandler无法捕获UserAlreadyExistsException
首先明确核心问题:你的异常根本没被抛出,所以异常处理器自然捕获不到。原因出在服务层的用户存在性判断逻辑上:
你用了userRepository.existsById(Long.valueOf(userDTO.getUsername())),但existsById是根据主键ID查询是否存在,而非用户名。哪怕用户名是数字字符串,判断的也是ID等于该数字的用户是否存在,而非用户名重复。这就导致即使用户名重复,这个判断也返回false,不会抛出UserAlreadyExistsException,所以接口一直返回201。
修复步骤
1. 修正服务层的存在性判断逻辑
首先在你的UserRepository接口中添加根据用户名查询存在性的方法:
public interface UserRepository extends JpaRepository<UserEntity, Long> { boolean existsByUsername(String username); }
然后修改服务层的createUser方法:
@Override public UserDTO createUser(UserDTO userDTO) { // 替换原有的existsById判断,改用根据用户名查询 var databaseUser = userRepository.existsByUsername(userDTO.getUsername()); if (databaseUser) { throw new UserAlreadyExistsException("User with this username already exists!"); } userDTO.setId(null); var userEntity = mapToUserEntity(userDTO); return mapToUserDTO(userRepository.save(userEntity)); }
2. 确认自定义异常的类型
确保UserAlreadyExistsException继承自RuntimeException(运行时异常),这样不需要在方法上声明throws,Spring才能自动捕获并交给异常处理器处理:
public class UserAlreadyExistsException extends RuntimeException { public UserAlreadyExistsException(String message) { super(message); } }
3. 验证异常处理器的扫描范围
确保UserExceptionHandler所在的包被Spring Boot启动类扫描到,比如启动类的包是com.example.demo,异常处理器在com.example.demo.exception下,这样默认会被扫描。如果不在同一包路径下,需要在启动类上添加@ComponentScan(basePackages = "com.example.demo.exception")指定扫描路径。
4. 补充接口文档的响应状态
在控制器的@ApiResponses中添加409状态码的说明,符合REST规范:
@ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Created"), @ApiResponse(responseCode = "400", description = "Client error"), @ApiResponse(responseCode = "409", description = "User already exists"), @ApiResponse(responseCode = "500", description = "Server error") })
测试验证
现在重复提交相同用户名的请求,应该会触发UserAlreadyExistsException,异常处理器会捕获并返回409状态码和对应的ErrorDTO内容。
内容的提问来源于stack exchange,提问作者Jack

