Spring WebFlux基于ReactiveCrudRepository实现Converter的问题
解决Spring WebFlux中ReactiveCrudRepository适配Post-Redirect-Get模式的Converter问题
我来帮你搞定这个问题!你遇到的报错本质是WebFlux的非阻塞线程模型不允许阻塞操作——原来Spring MVC里用CrudRepository是同步调用,block()没问题,但切换到ReactiveCrudRepository后,所有操作都是异步的,在Reactor线程里调用block()会直接破坏WebFlux的设计原则,所以被禁止了。
结合你的Thymeleaf表单绑定需求(要把字符串ID列表自动转成List<Obj>),我给你两个最实用的解决方案:
方案一:控制器层面手动做异步转换(最直接)
不用再纠结自定义Converter了,直接在控制器里接收表单提交的ID列表,异步查询后设置到实体对象里,全程非阻塞:
@PostMapping public Mono<String> processOrder(@ModelAttribute("newCar") Car car, @RequestParam("objs") List<String> objIds) { // 异步查询所有对应ID的Obj,转成List<Obj> return objRepository.findAllById(objIds) .collectList() .doOnNext(objs -> car.setObjs(objs)) .then(Mono.defer(() -> { // 执行表单验证 webDataBinder.validate(); BindingResult bindingResult = webDataBinder.getBindingResult(); if (bindingResult.hasErrors()) { return Mono.just("orderForm"); } // 保存后返回重定向,完美适配Post-Redirect-Get return carRepository.save(car).thenReturn("redirect:/"); })); }
为什么这个方案可行?
findAllById(objIds)返回Flux<Obj>,用collectList()异步收集成Mono<List<Obj>>,不会阻塞任何线程doOnNext()在结果就绪后才把Obj列表设置到Car里,完全符合异步流程Mono.defer()确保验证和保存操作在查询完成后执行,逻辑连贯且非阻塞
方案二:给实体加临时ID字段(保持表单自动绑定)
如果不想修改控制器参数,希望保持表单和实体的自动绑定,可以给Car类加一个临时的ID列表字段,专门用来接收表单提交的字符串ID,然后在控制器里异步转换:
第一步:修改Car实体类
@Document(collection = "cars") @ToString @EqualsAndHashCode public class Car { @Id private String id; private List<Obj> objs = new ArrayList<>(); // 临时字段,只用来接收表单数据,不会存到数据库 @Transient private List<String> objIds; // 记得加对应的getter和setter }
第二步:调整控制器逻辑
@PostMapping public Mono<String> processOrder(@ModelAttribute("newCar") Car car) { // 用临时字段里的ID列表异步查询Obj Mono<List<Obj>> objsMono = objRepository.findAllById(car.getObjIds()) .collectList(); return objsMono.flatMap(objs -> { car.setObjs(objs); // 表单验证逻辑和原来一致 webDataBinder.validate(); BindingResult bindingResult = webDataBinder.getBindingResult(); if (bindingResult.hasErrors()) { return Mono.just("orderForm"); } // 保存后返回重定向 return carRepository.save(car).thenReturn("redirect:/"); }); }
说明
@Transient注解告诉MongoDB不要把这个字段持久化到数据库里- 表单里只需把参数名改成
objIds(比如多选框的name="objIds"),就能自动绑定到这个临时字段 - 全程都是异步操作,完全符合WebFlux的非阻塞要求,不会再出现
block()相关的报错
为什么原来的Converter方案不行?
因为Spring的Converter接口是同步的,它的convert()方法必须立即返回结果,而你用ReactiveCrudRepository查询得到的是Mono<Obj>,只能通过block()强行同步获取结果——但这正好违反了WebFlux的非阻塞规则,所以才会报错。
除非你能找到支持异步的转换器接口,但Spring目前并没有提供这样的原生接口,所以上面两个方案是最靠谱的选择。
内容的提问来源于stack exchange,提问作者lambda253
相关产品推荐
相关产品推荐

