Spring MVC迁移Spring Reactive后实体状态未变更问题求助
问题根源分析
你的代码里有几个核心问题导致实体状态没能同步到数据库:
- 阻塞调用破坏Reactive上下文:
visitRequestDao.findById(requestId).get()是同步阻塞调用,在Reactive环境(比如Netty线程)中执行会导致Hibernate Session提前关闭——同步JPA操作完成后Session通常会被立即释放,后续的request.approve()修改发生在Session关闭之后,Hibernate无法捕获这些脏数据变更。 - 缺少Reactive事务支持:传统的
@Transactional注解是为同步场景设计的,在Reactive流中无法正确管理事务边界,导致修改后的实体没有被提交到数据库。 - Reactive流与JPA同步API不兼容:原生JPA是同步模型,直接在Reactive流中调用同步DAO方法会打破Session和事务的生命周期管理。
具体解决方案
1. 切换到Reactive持久化API(推荐:Hibernate Reactive)
如果要在Spring Reactive中继续使用Hibernate,建议迁移到Hibernate Reactive,它提供了原生的Reactive API,完美适配Reactor的Mono/Flux。
步骤:
- 添加Hibernate Reactive依赖(Maven示例):
<dependency> <groupId>org.hibernate.reactive</groupId> <artifactId>hibernate-reactive-core</artifactId> <version>2.2.2.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <!-- 排除同步Hibernate依赖 --> <exclusions> <exclusion> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> </exclusion> </exclusions> </dependency>
- 配置Reactive数据源和事务管理器:
@Configuration public class ReactiveHibernateConfig { @Bean public HibernateReactiveSessionFactory sessionFactory(EntityManagerFactory emf) { return emf.unwrap(HibernateReactiveSessionFactory.class); } @Bean public ReactiveTransactionManager reactiveTransactionManager(HibernateReactiveSessionFactory sessionFactory) { return new HibernateReactiveTransactionManager(sessionFactory); } }
- 重构DAO为Reactive风格:
public interface VisitRequestDao { Mono<VisitRequest> findById(Long requestId); Mono<VisitRequest> save(VisitRequest visitRequest); } @Repository public class VisitRequestDaoImpl implements VisitRequestDao { private final HibernateReactiveSessionFactory sessionFactory; public VisitRequestDaoImpl(HibernateReactiveSessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @Override public Mono<VisitRequest> findById(Long requestId) { return Mono.fromCompletionStage(sessionFactory.withSession(session -> session.find(VisitRequest.class, requestId))); } @Override public Mono<VisitRequest> save(VisitRequest visitRequest) { return Mono.fromCompletionStage(sessionFactory.withSession(session -> session.persist(visitRequest).thenApply(v -> visitRequest))); } }
2. 修正Reactive流中的事务与Session管理(兼容同步JPA过渡方案)
如果暂时无法切换到Hibernate Reactive,需要解决同步JPA调用的线程问题和事务问题:
修正后的代码示例:
@Service public class VisitRequestService { private final VisitRequestDao visitRequestDao; private final ReactiveTransactionTemplate transactionTemplate; public VisitRequestService(VisitRequestDao visitRequestDao, ReactiveTransactionTemplate transactionTemplate) { this.visitRequestDao = visitRequestDao; this.transactionTemplate = transactionTemplate; } public Mono<ResponseEntity<List<PendingVisitRequest>>> approveRequest(Long requestId, Owner owner) { return transactionTemplate.execute(status -> // 将同步JPA调用切换到弹性线程池,避免阻塞Netty EventLoop Mono.fromSupplier(() -> visitRequestDao.findById(requestId).get()) .subscribeOn(Schedulers.boundedElastic()) .map(request -> { request.approve(); return request; }) .doOnNext(visitRequestDao::save) ).map(request -> ResponseEntity.ok(listOfPendingVisitRequest(owner))); } }
3. 关键注意事项
- 永远不要在Reactor的EventLoop线程中执行阻塞操作:所有同步JPA调用必须通过
subscribeOn(Schedulers.boundedElastic())切换到专门的阻塞线程池。 - 使用Reactive事务注解:如果使用Spring Boot 2.3+,可以使用
@Transactional配合Reactive事务管理器,或者明确指定@Transactional(transactionManager = "reactiveTransactionManager")。 - 确保修改操作在Session/事务范围内:Hibernate的脏检查仅在Session活跃且事务提交时生效,所以实体的修改必须在打开的Session和事务中完成。
内容的提问来源于stack exchange,提问作者Alberto Souza
相关产品推荐
相关产品推荐

