PanacheReactive定时任务抛出Session/EntityManager关闭异常
在Quarkus框架的@Scheduled标注的响应式定时方法中,注入@ApplicationScoped作用域的PanacheRepository执行简单查询,本地Docker部署PostgreSQL环境运行完全正常,部署到GCP + Cloud SQL环境时抛出Session关闭异常。
相关代码如下:
Repository类:
@ApplicationScoped public class MyRepo implements PanacheRepository<MyEntity> { }
定时任务类:
@Inject MyRepo repo; @Scheduled(every = "${cache.refreshRate}", delayed = "20s") public Uni<Void> scheduleRefresh() { return repo.listAll().replaceWithVoid(); }
运行抛出的异常栈:
(vert.x-eventloop-thread-0) Error occurred while executing task for trigger IntervalTrigger [id=1_org.xx.xxxxx.infrastructure.importer.Importer_ScheduledInvoker_scheduleRefresh_520a27e95be32ee7cfd3163651929119f1ff17fe, interval=300000]: java.lang.IllegalStateException: Session/EntityManager is closed
at org.hibernate.internal.AbstractSharedSessionContract.checkOpen(AbstractSharedSessionContract.java:407)
at org.hibernate.engine.spi.SharedSessionContractImplementor.checkOpen(SharedSessionContractImplementor.java:148)
at org.hibernate.reactive.session.impl.ReactiveSessionImpl.checkOpen(ReactiveSessionImpl.java:1558)
at org.hibernate.internal.AbstractSharedSessionContract.checkOpenOrWaitingForAutoClose(AbstractSharedSessionContract.java:413)
这个问题本质是Hibernate Reactive会话生命周期和定时任务执行上下文不匹配,本地环境因为数据库网络延迟极低,时序竞争问题被隐藏,云端Cloud SQL的网络延迟、连接初始化耗时更长,直接触发问题:
@Scheduled响应式方法默认运行在Vert.x事件循环线程上,该线程默认没有绑定请求/事务级别的Hibernate会话上下文- 没有显式声明事务边界的情况下,Panache不会为定时方法的响应式流全程持有活跃会话,会话可能在查询实际执行前就被回收关闭
- Cloud SQL相比本地Docker部署的数据库,连接建立、IO响应延迟高很多,原本本地不会出现的会话提前关闭的时序竞争问题,在云端会稳定触发。
按优先级选择以下方案即可解决:
- 显式添加事务注解绑定会话生命周期
给定时方法加上@Transactional注解,Quarkus会为整个Uni响应式流的执行全程持有独立的活跃会话,方法执行完成前不会关闭会话,只读查询也支持该注解:@Transactional @Scheduled(every = "${cache.refreshRate}", delayed = "20s") public Uni<Void> scheduleRefresh() { return repo.listAll().replaceWithVoid(); } - 指定定时任务运行在工作线程
给@Scheduled注解添加executeOn = Scheduled.ExecuteOn.WORKER_THREAD参数,让任务脱离事件循环线程运行,配合事务注解可以彻底避免上下文缺失问题:@Transactional @Scheduled( every = "${cache.refreshRate}", delayed = "20s", executeOn = Scheduled.ExecuteOn.WORKER_THREAD ) public Uni<Void> scheduleRefresh() { return repo.listAll().replaceWithVoid(); } - 手动绑定会话上下文
如果是纯只读查询不需要开启事务,可以用Panache提供的会话包裹方法,确保查询执行时能拿到活跃会话:@Scheduled(every = "${cache.refreshRate}", delayed = "20s") public Uni<Void> scheduleRefresh() { return Panache.withSession(() -> repo.listAll()).replaceWithVoid(); }
- 针对Cloud SQL场景调整响应式连接池配置,设置合理的空闲连接回收时长、连接获取超时时间,避免云端网络波动导致连接意外断开连带会话关闭
- 所有涉及数据库操作的定时任务,都不要依赖隐式的会话上下文,必须显式声明事务/会话边界,不要因为本地运行正常就忽略配置
- 不要在Bean初始化阶段提前获取Session引用,每次定时任务执行时都通过上下文获取活跃会话,避免持有已关闭的会话实例。
内容的提问来源于stack exchange,提问作者Blackbelt

