Spring WebFlux新增用户无法插入MongoDB的问题排查
问题描述
基于Spring Boot Reactive Web搭建REST API,搭配MongoDB数据库。UserHandler的addUser方法逻辑为:先检查邮箱是否存在,存在则抛出UserAlreadyExistsException;不存在则插入用户。但调用该方法新增用户时,会返回400错误(Invalid request content)且用户无法插入;若简化方法直接插入则正常。
相关代码如下:
UserHandler的addUser方法
public Mono<User> addUser(Mono<User> user) { return user .flatMap(u -> repo.findByEmail(u.getEmail()) .flatMap(existingUser -> Mono.error( new UserAlreadyExistsException(String.format("User with email '%s' already exists.", existingUser.getEmail()))))) .switchIfEmpty(user.flatMap(repo::insert)) .cast(User.class); }
UserController代码
@RestController @RequestMapping("/users") public class UserController { @Autowired private UserHandler userService; ... @PostMapping public Mono<User> addUser(@Valid @RequestBody Mono<User> user) { return userService.addUser(user); } ... }
请求体
{ "firstname": "Alice", "lastname": "Jones", "email": "alice.jones@example.com", "bio": "Lorem ipsum dolor sit amet", "address": { "street": "123 Main St", "city": "Anytown", "state": "CA", "zipCode": "12345", "country": "USA" }, "phoneNumber": "123-456-7890" }
此外还提供了UserRepository、User实体类以及全局异常处理器的代码。请问代码哪里出错了?
问题分析与修复
错误原因
- Mono重复订阅导致请求体读取异常:你在
addUser方法中多次订阅了入参Mono<User>——第一次在flatMap里获取用户对象查询邮箱,第二次在switchIfEmpty里再次用该Mono执行插入。Reactive编程中,Mono是冷序列,每次订阅都会重新执行上游逻辑(这里就是重新解析请求体),但Spring Web的@RequestBody Mono<User>只能被读取一次,重复订阅会导致请求体已被消耗,从而抛出Invalid request content的400错误。 - 逻辑分支设计缺陷:当前代码中,
repo.findByEmail返回空时,flatMap的结果是Mono.empty(),此时进入switchIfEmpty但复用了原Mono,进一步触发重复订阅问题。
修复后的代码
方案一:缓存用户对象避免重复订阅
public Mono<User> addUser(Mono<User> user) { // 缓存Mono结果,确保多次订阅不会重复解析请求体 return user.cache() .flatMap(u -> repo.findByEmail(u.getEmail()) .flatMap(existingUser -> Mono.error( new UserAlreadyExistsException(String.format("User with email '%s' already exists.", existingUser.getEmail()))) // 用户不存在时返回原用户对象,触发后续插入 .switchIfEmpty(Mono.just(u)))) .flatMap(repo::insert) .cast(User.class); }
方案二:简化逻辑分支,复用已获取的用户对象
public Mono<User> addUser(Mono<User> user) { return user.flatMap(u -> repo.findByEmail(u.getEmail()) .hasElement() .flatMap(exists -> { if (exists) { return Mono.error(new UserAlreadyExistsException(String.format("User with email '%s' already exists.", u.getEmail()))); } else { return repo.insert(u); } }) ); }
修复说明
- 方案一通过
cache()操作缓存Mono<User>的结果,确保后续订阅不会重复解析请求体;同时在用户不存在时,将原用户对象包装为Mono返回,避免再次依赖原Mono。 - 方案二直接在
flatMap中获取用户对象u,用hasElement()判断邮箱是否存在,后续逻辑直接复用u执行插入,从根源避免了重复订阅问题,逻辑更简洁。
内容的提问来源于stack exchange,提问作者Phillip Emanuel
相关产品推荐
相关产品推荐

