0

I use Java 8, Hibernate 5.1.0.Final, Guice 4.1.0, Jersey 1.19. I have several REST methods which may modify the same object - entity loaded from the database. When they run concurrently and modify the same item, I get:

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

Caused by: javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

Possible solutions:

Are there any other options? What's the best way to handle this problem?

UPDATE 1

I have tried to implemented retry:

public void changeItemName(Long id, String name){

    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()); 
        changeItemName(id, name);
    }
}

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)
Community
  • 1
  • 1
Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114

1 Answers1

1
  1. Synchronization by object in REST methods I would say it's not possible. Imagine the same service running on 2 nodes.

  2. Constantly refresh object state again is not an option. The update could happens just between 2 refreshing.

  3. This one is fine. CAS principle. While version is not correct attempt to save the changes. But actually it depends on your business logic. E.g. There is a User object and initial name was "name1". The first call changes it from "name1" to "name2". The second calls tries to change from "name1" to "name3". We can either (A) change the name suppressing the "name1"->"name2" update or (B) reject the change reporting to invoker to refresh to see actual state and apply the changes to actual state.

If we are trying to modify e.g. account balance (A) approach is not valid and we need (B).

You should decide what to do according to the business logic you need there.

UPDATE: It won't work this way. You need to catch it higher - where transaction is started and do it in a separate transaction.

Suppose you have

@Service
public class MyServiceImpl
    @Transactional
    public void changeItemName(Long id, String name) {
    }

where the optimistic lock exception could happen. If you call the method just from catch block a new transaction is not started because not proxies method is called.

Instead you can do something like this

@Service
public class MyServiceImpl
    @Autowired
    private MyService myService;
    @Transactional
    public void changeItemName(Long id, String name) {
      catch() {
        myService.changeItemName(id, name);
      }
    }
StanislavL
  • 56,971
  • 9
  • 68
  • 98
  • Tried to retry with the lib https://vladmihalcea.com/2013/11/15/optimistic-locking-retry-with-jpa/ but it didn't work. Now added try-catch for the exception and then retry the method but I face another problem - when it's retried, object is retrieved from a cache, version is not refreshed and I get infinite loop. – Justinas Jakavonis May 03 '17 at 13:32
  • Tried to refresh, merge, evict, clear before retrying but didn't help. – Justinas Jakavonis May 03 '17 at 14:10
  • I think you should catch the exception, then evict (I suppose it should work) old object, get the object once more (entityManager.refresh(entity) ), apply changes and save it. Check that you do not copy version somehow. – StanislavL May 03 '17 at 14:16
  • After reload/refresh, I get updated object but it can't be persisted because of another JPA/Hibernate exception. – Justinas Jakavonis May 03 '17 at 15:44
  • Implemented like suggested but still get the same exception. Using @com.google.inject.persist.Transactional – Justinas Jakavonis May 04 '17 at 08:05
  • Created separate question with exact config: http://stackoverflow.com/questions/43780675/error-when-retrying-method-org-postgresql-util-psqlexception-this-statement-h – Justinas Jakavonis May 05 '17 at 11:30