0

I'm learning Spring JPA and Hibernate. So I faced a problem.

I have this method

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void sendMoney(Long from, Long to, Double amount) {
    WalletEntity fromWallet = walletServiceImpl.getWallet(from);
    WalletEntity toWallet = walletServiceImpl.getWallet(to);
    fromWallet.setAmount(fromWallet.getAmount() - amount);
    toWallet.setAmount(toWallet.getAmount() + amount);

    TransactionEntity transaction = new TransactionEntity();
    transaction.setAmount(amount);
    transaction.setToWallet(toWallet);
    transaction.setFromWallet(fromWallet);

    transactionRepository.saveAndFlush(transaction);
}

I wanted to test it and created this:

@GetMapping("/send")
public void sendMoney() {
    ExecutorService executorService = Executors.newFixedThreadPool(20);
    for (int i = 0; i < 100; i++) {
        executorService.execute(() -> {
            accountServiceImpl.sendMoney(1L, 2L, 10D);
        });
    }
}

So when I read wallet, I get the old value but I made Isolation.REPEATABLE_READ. The values in database are wrong of course. Can you explain what's wrong? Thank you!

1 Answers1

0

The isolation level REPTEABLE_READ works as intended.

You can get a good explanation here:

Spring @Transactional - isolation, propagation

But in order to clarify, this is what is happens:

                      Tx1      Tx2
                       |        |
Tx1 Read Wallet 1     100       |
Tx2 Read Wallet 1      |       100
Tx1 Discounts 10     100-10     |
Tx2 Discounts 10       |      100-10
Tx1 Commits            |        |
Tx2 Commits            |        |
Tx1 Read Wallet 1      90       |
Tx2 Read Wallet 2      |        90

So in order to control this behavior you have two options:

  1. Use serializable transaction level that block the operation in order to process one by one (this has performance penalty)
  2. Implement optimistic locking (the second transaction will throw an exception if it try to modify the same account at same time)

You can start reviewing optimistic locking here: Optimistic locking in JPA how does it work?

Ernesto Campohermoso
  • 7,213
  • 1
  • 40
  • 51
  • I've tried to use serializable level, but it failed with deadlock `Deadlock found when trying to get lock; try restarting transaction` – Stis Rapidum Sep 26 '18 at 14:11
  • Check this answer: https://stackoverflow.com/questions/17747906/getting-deadlock-found-when-trying-to-get-lock-try-restarting-transaction. But I believe you must deal with optimistic locking (you must avoid two modification to an wallet at same time). – Ernesto Campohermoso Sep 26 '18 at 14:12