@Transactional方法执行后出现PSQLException(语句已关闭)问题问询
PostgreSQL PSQLException: "This statement has been closed" 异常分析
问题场景
- 抛出
org.postgresql.util.PSQLException,提示内容:This statement has been closed. - 仅在APM监控中捕获到该异常,所有HTTP请求均返回200成功状态码
- 异常发生在
@Transactional方法执行后,方法内执行了foo.getBars().clear();和foo.getBars().addAll(bars);操作 - Bars数据已成功插入数据库
实体映射配置
@Fetch(FetchMode.SUBSELECT) @OneToMany( cascade = {CascadeType.ALL}, fetch = FetchType.EAGER, orphanRemoval = true) @JoinColumn(name = "foo_id", nullable = false) private List<Bars> bars= new ArrayList<>();
排查信息
- 被关闭的是Bars的insert语句
- 异常已被框架正确处理,调试日志显示:
Exception clearing maxRows/queryTimeout ... - 堆栈信息:
org.postgresql.jdbc.PgStatement.checkClosed(PgStatement.java:745) org.postgresql.jdbc.PgStatement.getMaxRows(PgStatement.java:548) com.zaxxer.hikari.pool.HikariProxyPreparedStatement.getMaxRows(HikariProxyPreparedStatement.java) org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl.close(ResourceRegistryStandardImpl.java:193) org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl.release(ResourceRegistryStandardImpl.java:108) org.hibernate.engine.jdbc.mutation.internal.PreparedStatementDetailsStandard.releaseStatement(PreparedStatementDetailsStandard.java:69) org.hibernate.engine.jdbc.mutation.internal.MutationExecutorPostInsertSingleTable.release(MutationExecutorPostInsertSingleTable.java:115) org.hibernate.persister.entity.mutation.InsertCoordinator.doStaticInserts(InsertCoordinator.java:186) org.hibernate.persister.entity.mutation.InsertCoordinator.coordinateInsert(InsertCoordinator.java:111) org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2779) org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81) org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:676) org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:291) org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:272) org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:322) org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:363) org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:277) org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180) org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:140) org.hibernate.event.internal.DefaultMergeEventListener.saveTransientEntity(DefaultMergeEventListener.java:314) org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:233) org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:152) org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:142) org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:126) org.hibernate.internal.SessionImpl$$Lambda$.applyEventToListener org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138) org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:869) org.hibernate.internal.SessionImpl.merge(SessionImpl.java:840) org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:253) org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:243) org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:513) org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:434) org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220) org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:547) org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:477) org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:437) org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220) org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153) org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:571) org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:212) org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:155) org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:142)
异常原因
这个异常是Hibernate、PostgreSQL JDBC驱动和HikariCP交互时的无害异常,本质是:
- 事务完成后,Hibernate在清理JDBC PreparedStatement资源时,调用
getMaxRows()检查状态 - 但此时HikariCP已经提前关闭了这个Statement(可能是连接被回收、或池的空闲超时触发)
- PostgreSQL驱动检测到Statement已关闭,抛出异常,但Hibernate的资源清理逻辑已经捕获并处理了这个异常,只会打一条调试日志,完全不影响业务
是否属于正常情况?
绝对正常。从当前情况来看:
- Bars数据已成功插入,业务逻辑无问题
- 所有请求均返回200,用户侧无感知
- 仅APM能捕获到该异常,框架已完成异常处理(日志中的
Exception clearing maxRows/queryTimeout ...就是处理证明)
这属于框架内部的“噪音异常”,不影响业务正确性。
是否需要修改Bars的持久化方式?
目前不需要修改,除非碰到以下情况:
- 异常频率过高,导致APM频繁告警或日志冗余
- 出现实际业务数据不一致的问题
如果想消除该异常,可以尝试以下优化方案:
- 调整HikariCP配置:适当延长
idleTimeout参数,避免连接在Hibernate完成资源清理前被回收 - 修改Fetch策略:将
FetchType.EAGER改为FetchType.LAZY,减少EAGER加载带来的会话资源占用时间(需注意懒加载可能引发的N+1问题,可配合@Fetch(FetchMode.JOIN)或批量加载优化) - 升级Hibernate版本:部分新版本Hibernate优化了资源清理逻辑,可能避免该异常抛出
- 升级PostgreSQL JDBC驱动:更新到最新稳定版,修复驱动层面的Statement状态检测逻辑
内容的提问来源于stack exchange,提问作者Hayi
相关产品推荐
相关产品推荐

