2

I have method:

public void changeItemName(long id, String nmae) {

    Item item = itemDAO.find(id);
    item.setName(name);

    try {
        itemDAO.save(item);
    } catch (RollbackException | OptimisticLockException | StaleStateException e) {
        logger.warn("Retry method after " + e.getClass().getName());
        itemDAO.clear();
        changeItemName(id, name);
    }
}

First, I manually provoke OptimisticLockException by setting higher version, so it goes to catch block, clears EntityManager and retries the method. When retrying, the object/entity is refreshed and has correct version but I get:

javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:86)

Caused by: javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not update: [com.example.Item#1]
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1692)

Caused by: org.hibernate.exception.GenericJDBCException: could not update: [com.example.Item#1]
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)

Caused by: org.postgresql.util.PSQLException: This statement has been closed.
at org.postgresql.jdbc2.AbstractJdbc2Statement.checkClosed(AbstractJdbc2Statement.java:2653)

Database module (using Guice 4.1.0):

public class DbModule extends PrivateModule {

    @Override
    public void configure() {
        install(new JpaPersistModule("persistence-unit").properties(jpaProperties()));

        ...

        Key<PersistFilter> key = Key.get(PersistFilter.class, ExamplePersistenceUnit.class);
        bind(key).to(PersistFilter.class);
        expose(key);
}

Save method implementation (using Hibernate 5.1.0.Final):

@Inject
protected EntityManager entityManager;

@Override
public void save(T entity) {
    entityManager.getTransaction().begin();
    entityManager.persist(entity);
    entityManager.getTransaction().commit();
}

Why does it happen?

UPDATE

After some debugging, I have noticed that:

  • 1st method invocation - I get OptimisticLockException / StaleStateException - that's ok, it was intentionally raised and expected
  • 2nd method invocation, retry, I get Caused by: org.postgresql.util.PSQLException: This statement has been closed - this one was unexpected
  • 3rd method invocation, another retry - it works well and saves to the DB successfully

All 3 times, the same instance of EntityManager entityManager is used.

Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114
  • What does `itemDAO.clear()` do? How do you manage your transactions? What does the `@Inject`ion? (Typically the `EntityManager` is injected with `@PersistenceUnit`) -- That error message happens, when a `PreparedStatement` is (re-)used, but the underlying connection is already closed. – pozs May 04 '17 at 10:51
  • itemDAO.clear() does entityManager.clear(). I use it for getting the most updated data from a database when doing second itemDAO.find(id). – Justinas Jakavonis May 04 '17 at 10:52
  • 1
    Thanks for the update about trying a third time after the PSQLException! We have Spring Retry wrapped around a transaction using optimistic locking. With `@Retryable(include = {ObjectOptimisticLockingFailureException.class, JpaSystemException.class}, maxAttempts = 4)` all of our concurrent update tests pass. If we don't retry on JpaSystemException (which wraps the PSQLException about the closed connection) then our tests fail with the closed statement error every time a retry happens. – Jonathan Fuerth May 19 '17 at 19:57

1 Answers1

2

I also encountered the same issue, and with the help of @Jonathan's comment, I solved it by using the: @Retryable(include = {ObjectOptimisticLockingFailureException.class, JpaSystemException.class}, backoff=@Backoff(delay = 100))

See also good Q&A: Spring @Retryable - how to log when it is invoked?