I am using spring Data . I have a problem with spring data transactions as follows:
The entity and repositories are as follows:
@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 ... }
repository as follows
public interface WalletJpaRepository extends JpaRepository<Wallet, Long>{ @Lock(LockModeType.OPTIMISTIC)
I am making a method call for two methods at the same time, as shown below:
@Test public void testConcurrentTransactions() { System.out.println("Wallet 1 : ->" + getWallet1()); System.out.println("Wallet 2 : ->" + getWallet2()); }
And these two methods are described below.
@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; }
The problem is that I am not getting updated values โโof the same object in different method calls.
for example, if the value of an object with identifier 1 has a value of 1000 initially, after calling the getWallet1 () method, the value should be updated to 1100, but it is not reflected in the second method, i.e. getWallet2 () and again I get 1000 in the second method, as described in the comments of the above code.
I tried with propagation , Isolation , Lock , but still I do not get the required results.
Is there a solution to handle such a scenerio, I can not find a solution to this situation. This is a simplified version of scenerio that I get in a huge system of money transactions, where the hit rate is from 4 to 5 transactions per second.
The above example is just an example in which I just tried to reproduce scenerio. Below is the code for the same.
@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() >= walletRequest.getAmount()) { try { User user = walletRequest.getRequestFrom(); UserWalletTransaction txn1 = new UserWalletTransaction(); UserWalletTransaction txn2 = new UserWalletTransaction(); 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()); 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); txn1.setRelationalTransaction(txn2); txn1.setBalanceAmount(wallet1.getVirtualBalance()); txn1.setStatus(Utility.statusPending); txn1 = walletTransactionJpaRepository.save(txn1); Wallet wallet2 = user.getWallet(); wallet2.setVirtualBalance(user.getWallet().getVirtualBalance() + walletRequest.getAmount()); wallet2 = walletJpaRepository.save(wallet2); txn2.setBalanceAmount(wallet2.getVirtualBalance()); txn1.setStatus(Utility.statusSuccess); txn2.setStatus(Utility.statusSuccess); txn2.setRelationalTransaction(txn1); List<UserWalletTransaction> transactions = new ArrayList<>(); 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 { 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; } }
And another method that works with the same object is shown below
@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); 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); }
Similarly, in different services that access the wallet, there are seven methods at the same time that can simultaneously be registered as users, and the likelihood that the user administrator will also log into the system and perform money transactions, that is, a real scenerio, where we get this a problem.
Thank you in advance