Spring Boot中Webflux+MapStruct实现用户创建的问题排查
Spring Webflux + R2DBC 用户创建接口:异常处理与空指针问题修复
场景说明
基于Spring Boot,使用Webflux结合R2DBC(PostgreSQL)实现用户创建接口,定义四类对象处理逻辑:
UserDetailsRequestModel:接收前端请求体UserResponseModel:返回给前端的响应结果UserDto:控制器与服务层之间的数据传输对象UserEntity:数据库持久化实体
通过MapStruct实现对象转换,仓库基于ReactiveCrudRepository。
核心代码实现
用户创建请求体(UserDetailsRequestModel)
public class UserDetailsRequestModel { private String firstName; private String lastName; private String email; private String password; }
返回对象(UserResponseModel)
public class UserResponseModel { private String userId; private String firstName; private String lastName; private String email; }
数据传输对象(UserDto)
public class UserDto implements Serializable { @Serial private static final long serialVersionUID = -386521462517601642L; private Long id; private String userId; private String firstName; private String lastName; private String email; private String password; private String encryptedPassword; private String emailVerificationToken; private Boolean emailVerificationStatus = false; }
数据库实体(UserEntity)
public class UserEntity implements Serializable { @Serial private static final long serialVersionUID = -5590905788591386398L; @Id private Long id; @Column @NotNull @Size(max = 50) private String userId; private String firstName; private String lastName; private String email; private String encryptedPassword; private String emailVerificationToken; private Boolean emailVerificationStatus = false; }
MapStruct转换接口(UserMapper)
@Mapper public interface UserMapper { UserMapper USERMAPPER = Mappers.getMapper(UserMapper.class); UserDto toUserDto(UserDetailsRequestModel userDetails); UserResponseModel toUserResponse(UserDto userDto); UserEntity toUserEntity(UserDto userDto); UserDto entityToUserDto(UserEntity userEntity); }
仓库接口(UserRepository)
修正原仓库方法参数错误,Reactive查询需接收具体参数类型而非Mono
@Repository public interface UserRepository extends ReactiveCrudRepository<UserEntity, Long> { Mono<UserEntity> findByEmail(String email); }
原控制器代码
@PostMapping( produces = MediaType.TEXT_EVENT_STREAM_VALUE ) public Mono<UserResponseModel> createUser(@RequestBody Mono<UserDetailsRequestModel> userDetailsRequestModelMono) { return userDetailsRequestModelMono .map(userDetailsRequestModel -> UserMapper.USERMAPPER.toUserDto(userDetailsRequestModel)) .map(userDto -> { Mono<UserDto> userDtoMono = this.userService.createUser(Mono.just(userDto)); System.out.println("UserDto > " + userDto.toString()); return userDtoMono; }) .flatMap(userDtoMono -> { Mono<UserResponseModel> userResponseModelMono = userDtoMono.map(userDtoResponse -> { UserResponseModel userResponseModel = UserMapper.USERMAPPER.toUserResponse(userDtoResponse); System.out.println("UserResponseModel > " + userResponseModel.toString()); return userResponseModel; }); return userResponseModelMono; }) .doOnError(err -> System.out.println("Error caught >> " + err)) .doFinally(System.out::println); }
原服务层实现
@Override public Mono<UserDto> createUser(Mono<UserDto> userDtoMono) { return userDtoMono .map(userDto -> UserMapper.USERMAPPER.toUserEntity(userDto)) .flatMap(userEntity -> { if (userRepository.findByEmail(Mono.just(userEntity.getEmail())) == null) { userEntity.setUserId("azvxcvxcxcvcx"); userEntity.setVersion(1L); userEntity.setEmailVerificationToken("emailVerifToken"); userEntity.setEmailVerificationStatus(Boolean.FALSE); userEntity.setEncryptedPassword("encryptedPassword"); System.out.println("UserEntity > " + userEntity.toString()); return userRepository.save(Mono.just(userEntity)); } else { return null; } }) .map(userEntity -> { UserDto userDto = UserMapper.USERMAPPER.entityToUserDto(userEntity); System.out.println(userDto); return userDto; }); }
遇到的问题
- 服务层无法正确处理用户已存在的场景,需实现异常抛出逻辑
- 运行时触发
java.lang.NullPointerException: The mapper returned a null Mono异常
问题分析与解决方案
1. 空指针异常原因及修复
错误根源
- 原仓库方法参数不符合Reactive规范,
findByEmail应接收String而非Mono<String> - 服务层中
if (userRepository.findByEmail(...) == null)判断错误:Mono永远不会为null,无查询结果时返回Mono.empty() - else分支返回
null,导致flatMap接收null值触发空指针异常
修复后的服务层代码
// 自定义用户已存在异常 public class UserAlreadyExistsException extends RuntimeException { public UserAlreadyExistsException(String message) { super(message); } } // 服务层实现 @Override public Mono<UserDto> createUser(UserDto userDto) { UserEntity userEntity = UserMapper.USERMAPPER.toUserEntity(userDto); return userRepository.findByEmail(userEntity.getEmail()) // 查询到用户则抛出异常 .flatMap(existingUser -> Mono.error(new UserAlreadyExistsException("邮箱" + userEntity.getEmail() + "已被注册"))) // 未查询到则继续执行保存逻辑 .then(Mono.just(userEntity)) .map(entity -> { // 生成唯一userId,建议用UUID entity.setUserId(UUID.randomUUID().toString()); entity.setEncryptedPassword("encryptedPassword"); // 实际项目需用加密算法处理密码 entity.setEmailVerificationToken("emailVerifToken"); entity.setEmailVerificationStatus(false); return entity; }) // 保存实体 .flatMap(userRepository::save) // 转换为Dto返回 .map(UserMapper.USERMAPPER::entityToUserDto); }
简化后的控制器代码
@PostMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Mono<UserResponseModel> createUser(@RequestBody Mono<UserDetailsRequestModel> userDetailsRequestModelMono) { return userDetailsRequestModelMono .map(UserMapper.USERMAPPER::toUserDto) .flatMap(this.userService::createUser) .map(UserMapper.USERMAPPER::toUserResponse) .doOnError(err -> System.err.println("Error caught >> " + err)) .doFinally(System.out::println); }
2. 用户已存在的异常处理
添加全局异常处理器,返回友好的HTTP响应:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UserAlreadyExistsException.class) public ResponseEntity<ErrorResponse> handleUserAlreadyExists(UserAlreadyExistsException ex) { ErrorResponse error = new ErrorResponse( HttpStatus.CONFLICT.value(), ex.getMessage(), LocalDateTime.now() ); return new ResponseEntity<>(error, HttpStatus.CONFLICT); } } // 错误响应实体 public class ErrorResponse { private int statusCode; private String message; private LocalDateTime timestamp; public ErrorResponse(int statusCode, String message, LocalDateTime timestamp) { this.statusCode = statusCode; this.message = message; this.timestamp = timestamp; } // getter方法 public int getStatusCode() { return statusCode; } public String getMessage() { return message; } public LocalDateTime getTimestamp() { return timestamp; } }
内容的提问来源于stack exchange,提问作者davidvera
相关产品推荐
相关产品推荐

