Search code examples
javapostgresqlspring-boothibernatejpa

Swallowed PSQLException with "the statement that has been close"


There is an exception org.postgresql.util.PSQLException with This statement has been closed.

this exception only appear in our APM, and all http requests are succeessfull with status code 200,

it occurs after the execution of @Transactional method

foo.getBars().clear();
foo.getBars().addAll(bars);

with the mapping defined like this

  @Fetch(FetchMode.SUBSELECT)
  @OneToMany(
      cascade = {CascadeType.ALL},
      fetch = FetchType.EAGER,
      orphanRemoval = true)
  @JoinColumn(name = "foo_id", nullable = false)
  private List<Bars> bars= new ArrayList<>();

the Bars are successfully inserted,

I just want to know why this behaviour, is it perfectly normal or I have to change something in the way i persist Bars ?

the statement that has been close is the insert statement of Bars

when I check the code I notice that the exception is thrown but it is handled correctly with a debug message Exception clearing maxRows/queryTimeout ...

org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl.close(ResourceRegistryStandardImpl.java:193)

enter image description here

Stacktrace:
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)

Solution

  • is it perfectly normal or I have to change something in the way i persist Bars ?

    It is, you don't. Reading into the code you're showing, it looks like some logic on best effort basis:

    1. Grab the prepared statement.
    2. Before attempting to close it, inspect it and clean it. The check with getters is as important as the cleanup using setters - each of the four can fail, telling you more about its state. Here you can see the getter failed.
    3. If cleanup succeeds, it's now free to be closed outside try{}.
    4. If try{} failed, it's assumed to be closed already.

    Either way, this code reaches its desired goal of making sure the statement is in closed state, even if along the way it realizes it's not the one that ended up actually closing it. It's trying to get it closed, it "fails" because it's already closed - so it catches that and reports a success, which makes sense.

    I'd be worried if you were seeing anything else than This statement has been closed. in the org.postgresql.util.PSQLException raised there because the try{}catch(){} doesn't do anything to make sure that's really the case - it'll catch and ignore anything database complains about. The question you could ask instead is what other SQL exceptions than This statement has been closed you can possibly get in there: given that it's only manipulating statement config, except for the "it's already closed" you won't get anything that's worth handling there and/or won't manifest anywhere else.


    You could also just check statement.isClosed(), but it's already included in the behaviour of the four getters/setters:

    Throws: SQLException - if a database access error occurs, this method is called on a closed Statement