0

Here is the sample project where the exception is reproduced.

This sample illustrates the issue when many concurrent transactions are modifiying Account balance. Account can have many Card entities bound. Transactions are related to Order and last in time. Each Thread executes as follows:

  1. client requests '/order/{hashId}' for first available Order by given card hash id
  2. client starts new tx for given order - '/tx/{orderId}/start'
  3. client completes tx - '/tx/{txId}/stop/{amount}' where the tx amount is subtracted from Account balance.

Entity Locking

Account and Order entities are versioned with @javax.persistence.Version. In last step Account entity is locked with pessimistic write lock:

@Override
public Account getLockedAccount(Integer id) {
    final Account account = findOne(id);
    em.lock(account, LockModeType.PESSIMISTIC_WRITE);
    return account;
}

Testing

To test the concurrent access use JMeter script src/main/resources/StressTest.jmx. NB: Extra libs have to be installed to JMeter home to run the script due to usage of JSON Path extractor. With these specific settings on an average laptop you can get around 10% of errors for TxEnd request:

{  
"timestamp":1425407408204,
"status":500,
"error":"Internal Server Error",
"exception":"org.springframework.orm.ObjectOptimisticLockingFailureException",
"message":"Object of class [sample.data.jpa.domain.Account] with identifier [1]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [sample.data.jpa.domain.Account#1]",
"path":"/tx/1443/stop/46.4"
}

Question

Despite of using pessimistic write lock I still get the optimistic locking exception. Is there any other approach to ensure the integrity of account without creating a task execution queue for all updates or synchronizing methods?

UPD: The work around with task executor is placed in another branch. Spring ThreadPoolTaskExecutor combined with transactional task remediates the issue.

E.G.
  • 181
  • 2
  • 9

1 Answers1

0

Between find and locking, the Account object may have been already modified. You need to do it in one statement

EM.find(Account.class, id, LockModeType.PESSIMISTIC_WRITE)

Zielu
  • 8,312
  • 4
  • 28
  • 41
  • Before using EntityManager I tried @Lock annotation: `@Lock(LockModeType.PESSIMISTIC_WRITE) Account findOne(Integer integer);`. Full implementation of previous version can be found [here](https://github.com/EvgeniGordeev/spring-jpa-optimistic-lock/blob/dedc33d5f5462819e1b5fb7ce165d14e27480b96/src/main/java/sample/data/jpa/service/AccountRepository.java). Inside Spring SimpleJpaRepository the same equivalent `em.find(domainType, id, type, hints)` is used where hints is current LockModeType. – E.G. Mar 04 '15 at 02:51