Concurrent transaction processing in spring data

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 have also tried PESSIMISTIC, READ, WRITE, PESSIMISTIC_READ, PESSIMISTIC_WRITE, etc.but they don't seem to work Wallet findOne(Long id); } 

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(); /** * 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<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 { /** * 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; } } 

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); /** * 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); } 

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

+5
source share
1 answer

Hi guys, I'm going to answer my question, this may help someone in the future, I found a solution to my problem. Thanks to Denium for pointing out the problem. This is a really great concept.

The error I made was internal method calls and writing @Transactional to private methods.

@Transactional is implemented using spring AOP , so calls to the internal method never reach the proxy, and the behavior of @Transactional functionality is simpler.

So, the solution was to wrap the methods in the object and define @Transactional in the methods of the object and only make external calls to the object.

Other solutions may define our own point cuts and advice

For more information, visit the following links:

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html https://www.mkyong.com/spring/spring-aop-example-pointcut-advisor/

Please feel free to add any suggestions and changes,

thanks

+4
source

All Articles