title: Handling concurrent transactions in spring data
body: <p>I am using <code>spring Data</code>.
  I have a problem with the spring data concurrent transactions as follows:</p>
  
  <p>The entity and the repositories are as follows:</p>
  
  <pre><code>    @Entity
      public class Wallet {
  
          @Version
          private int version;
          @Id
          @GeneratedValue
          @OrderColumn
          private Long Id;
          @OneToOne()
          @OrderColumn
          private User user;
          @OrderColumn
          private Double virtualBalance;
          @Column(name = "created_by")
          @OrderColumn
          private String createdBy;
          @Column(name = "created_date")
          @OrderColumn
          private Date createdDate;
          @Column(name = "updated_by")
          @OrderColumn
          private String updatedBy;
          @Column(name = "updated_date")
          @OrderColumn
          private Date updatedDate;
  ... Setters and getters ...
  }
  </code></pre>
  
  <p>The <code>repository</code> is as follows </p>
  
  <pre><code>public interface WalletJpaRepository extends JpaRepository&lt;Wallet, Long&gt;{
  
      @Lock(LockModeType.OPTIMISTIC) // I have also tried PESSIMISTIC, READ, WRITE, PESSIMISTIC_READ, PESSIMISTIC_WRITE, etc.but they don't seem to work
      Wallet findOne(Long id);
  
  }
  </code></pre>
  
  <p><strong>I am making a method call to two of the methods concurrently as shown below:</strong></p>
  
  <pre><code>@Test
      public void testConcurrentTransactions() {
          System.out.println("Wallet 1 : -&gt;" + getWallet1());
          System.out.println("Wallet 2 : -&gt;" + getWallet2());
      }
  </code></pre>
  
  <p>And the two methods are as described below </p>
  
  <pre><code>@Transactional(isolation = Isolation.SERIALIZABLE)
  private Wallet getWallet1() {
      Wallet wallet1 = walletJpaRepository.findOne(new Long(1)); // suppose the value of wallet1.getVirtualBalance() is 1000
      wallet1.setVirtualBalance(wallet1.getVirtualBalance().doubleValue() + 100); // After evaluating this line it becomes 1100
      System.out.println(Thread.currentThread().getId());
      return wallet1;
  }
  
  @Transactional(isolation = Isolation.SERIALIZABLE)
  private Wallet getWallet2() {
      Wallet wallet2 = walletJpaRepository.findOne(new Long(1)); // Here again the value of wallet2.getVirtualBalance() fetched is 1000 but I need 1100 to be the value read
      System.out.println(Thread.currentThread().getId());
      return wallet2;
  }
  </code></pre>
  
  <p><strong>The problem is that I am not getting updated values of the same entity in different method calls.</strong></p>
  
  <p>for example if the value of the entity with id 1 has value of 1000 initially after calling the method getWallet1() the value should be updated to 1100 but it doesn't get reflected in the second method i.e. getWallet2() and again I get 1000 in the second method as explained in the comments of the code above.</p>
  
  <p>I have tried with <code>propagation</code>, <code>Isolation</code>, <code>Lock</code> but still I don't get the required results.</p>
  
  <p>Is there a solution to handle such a scenerio, I am unable to find a solution to such a situation, This is a simplified version of a scenerio which I am getting in a huge monetary transaction system, where the hit rate is about 4 to 5 transactions per second.</p>
  
  <p>The above is just an example in which I just tried to reproduce the scenerio, Below is the actual code for the same.</p>
  
  <pre><code>@Override
  @Transactional
  public InterWalletRequestFrontendWrapper approveOrDeclineRequest(User requestingUser, String operation,
          String requestId) {
  
      InterWalletRequest walletRequest = interWalletRequestJpaRepository.findOne(Long.parseLong(requestId));
      if (walletRequest.getStatus().equalsIgnoreCase(Utility.statusInitiated)
              || walletRequest.getStatus().equalsIgnoreCase(Utility.statusPending)) {
          if (operation.equalsIgnoreCase(Utility.operationDecline)) {
              walletRequest.setStatus(Utility.statusDeclined);
              interWalletRequestJpaRepository.save(walletRequest);
              InterWalletRequestFrontendWrapper response = fetchRaisedRequests(requestingUser);
              response.setStatus(0);
              response.setStatusDesc(Utility.statusDeclined);
              return response;
          } else {
  
              User admin = walletRequest.getRequestTo();
              Wallet adminWallet = admin.getWallet();
  
              if (adminWallet.getVirtualBalance() &gt;= walletRequest.getAmount()) {
                  try {
  
                      User user = walletRequest.getRequestFrom();
  
                      UserWalletTransaction txn1 = new UserWalletTransaction();
                      UserWalletTransaction txn2 = new UserWalletTransaction();
                      /**
                       * New transaction initiated for admin
                       */
                      txn1.setAmountTransacted(walletRequest.getAmount());
                      txn1.setDebitUser(admin);
                      txn1.setCreditUser(user);
                      txn1.setOperationPerformed(Utility.operationPerformedInterWallet);
                      txn1.setPreviousAmount(admin.getWallet().getVirtualBalance());
                      txn1.setStatus(Utility.statusNew);
                      txn1.setUser(admin);
                      txn1.setTransactionType(Utility.transactionTypeDebit);
                      txn1.setCreatedBy(admin.getUserName());
                      txn1.setUpdatedBy(admin.getUserName());
                      txn1.setCreatedDate(new Date());
                      txn1.setUpdatedDate(new Date());
                      txn1.setWallet(admin.getWallet());
  
                      /**
                       * New txn initiated for the user who walletRequested
                       * the txn.
                       */
                      txn2.setAmountTransacted(walletRequest.getAmount());
                      txn2.setDebitUser(admin);
                      txn2.setCreditUser(user);
                      txn2.setOperationPerformed(Utility.operationPerformedInterWallet);
                      txn2.setPreviousAmount(user.getWallet().getVirtualBalance());
                      txn2.setStatus(Utility.statusNew);
                      txn2.setTransactionType(Utility.transactionTypeCredit);
                      txn2.setCreatedBy(admin.getUserName());
                      txn2.setUpdatedBy(admin.getUserName());
                      txn2.setCreatedDate(new Date());
                      txn2.setUpdatedDate(new Date());
                      txn2.setUser(user);
                      txn2.setWallet(user.getWallet());
  
                      txn2 = walletTransactionJpaRepository.save(txn2);
  
                      Wallet wallet1 = admin.getWallet();
                      wallet1.setVirtualBalance(admin.getWallet().getVirtualBalance() - walletRequest.getAmount());
                      wallet1 = walletJpaRepository.save(wallet1);
  
                      /**
                       * After debit set the reference of other user.
                       */
  
                      txn1.setRelationalTransaction(txn2);
                      /**
                       * After debit from admin set balance amount
                       * 
                       */
                      txn1.setBalanceAmount(wallet1.getVirtualBalance());
  
                      /**
                       * Money deducted from admin wallet but not credited to
                       * the user wallet. so status is pending.
                       */
                      txn1.setStatus(Utility.statusPending);
                      txn1 = walletTransactionJpaRepository.save(txn1);
  
                      Wallet wallet2 = user.getWallet();
                      wallet2.setVirtualBalance(user.getWallet().getVirtualBalance() + walletRequest.getAmount());
                      wallet2 = walletJpaRepository.save(wallet2);
  
                      /**
                       * After credit to User wallet add balance amount.
                       */
                      txn2.setBalanceAmount(wallet2.getVirtualBalance());
  
                      txn1.setStatus(Utility.statusSuccess);
                      txn2.setStatus(Utility.statusSuccess);
                      txn2.setRelationalTransaction(txn1);
  
                      List&lt;UserWalletTransaction&gt; transactions = new ArrayList&lt;&gt;();
                      transactions.add(txn1);
                      transactions.add(txn2);
  
                      walletTransactionJpaRepository.save(transactions);
  
                      walletRequest.setStatus(Utility.statusApproved);
                      interWalletRequestJpaRepository.save(walletRequest);
  
                      InterWalletRequestFrontendWrapper response = fetchRaisedRequests(requestingUser);
                      response.setStatus(0);
                      response.setBalance(wallet1.getVirtualBalance());
                      response.setStatusDesc(Utility.statusApproved);
                      return response;
  
                  } catch (Exception e) {
                      System.out.println(".......... Exception Caught ..........");
                      walletRequest.setStatus(Utility.statusPending);
                      interWalletRequestJpaRepository.save(walletRequest);
                      InterWalletRequestFrontendWrapper response = fetchRaisedRequests(requestingUser);
                      response.setStatus(0);
                      response.setStatusDesc(Utility.statusDeclined);
                      return response;
                  }
              } else {
                  /**
                   * if the admin wallet desn't have enough balance then the
                   * status is set to pending.
                   */
                  walletRequest.setStatus(Utility.statusPending);
                  interWalletRequestJpaRepository.save(walletRequest);
                  InterWalletRequestFrontendWrapper response = fetchRaisedRequests(requestingUser);
                  response.setStatus(0);
                  response.setStatusDesc(Utility.statusDeclined);
                  return response;
              }
          }
      } else {
          InterWalletRequestFrontendWrapper response = fetchRaisedRequests(requestingUser);
          response.setStatus(0);
          response.setStatusDesc(Utility.statusDeclined);
          return response;
      }
  
  }
  </code></pre>
  
  <p><strong>And another method which operates on the same entity is shown below</strong></p>
  
  <pre><code>@Override
  @Transactional
  private UserWalletTransaction initiateVerifyTransaction(AccountsDetails transfer, User user) {
  
          Double amountTransacted = 2.00;
          Wallet wallet = user.getWallet();
          UserWalletTransaction transaction = new UserWalletTransaction();
          transaction.setAmountTransacted(amountTransacted);
  
          transaction.setPreviousAmount(wallet.getVirtualBalance());
          transaction.setOperationPerformed(Utility.operationPerformedDVBeneFundTransfer);
          transaction.setTransactionType(Utility.transactionTypeDebit);
  
          /**
           * Debit from wallet.
           */
          wallet.setVirtualBalance(wallet.getVirtualBalance() - amountTransacted);
          wallet.setUpdatedDate(new Date());
          wallet.setUpdatedBy(user.getUserName());
          wallet = walletJpaRepository.save(wallet);
          logger.info(wallet);
  
          transaction.setBalanceAmount(wallet.getVirtualBalance());
          transaction.setUser(user);
          transaction.setWallet(wallet);
          transaction.setStatus(Utility.statusNew);
          transaction.setCreatedBy(user.getUserName());
          transaction.setUpdatedBy(user.getUserName());
          transaction.setCreatedDate(new Date());
           transaction.setToAccount(transfer.getAccount());
           transaction.setBankName(transfer.getBankName());
           transaction.setBeniMobile(transfer.getRecipientMobileNo());
           transaction.setTransactionMode(transfer.getChannel().equalsIgnoreCase("2")
           ? "IMPS" : "NEFT");
          return walletTransactionJpaRepository.save(transaction);
  
      }
  </code></pre>
  
  <p><strong>Like this there are seven methods in different services which access the wallet at the same time as there can be number of users logged in at the same time, And probabilities are that the users admin is also logged in and performing monetary transactions, that is the real scenerio where we get this problem.</strong></p>
  
  <p>Thanks in advance</p>