2016-08-22 38 views
5

我正在使用spring Data。 我與春天的數據的併發事務處理的問題如下:處理春季數據中的併發交易

的實體和庫如下:

@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如下

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); 

} 

我正在同時調用兩種方法的方法如下所示:

@Test 
    public void testConcurrentTransactions() { 
     System.out.println("Wallet 1 : ->" + getWallet1()); 
     System.out.println("Wallet 2 : ->" + getWallet2()); 
    } 

而且這兩種方法如下

@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; 
} 

作爲描述的問題是,我不是在不同的方法調用得到相同的實體的更新值。

例如,如果在調用方法getWallet1()後,具有id 1的實體的值初始值爲1000,則該值應該更新爲1100,但不會反映在第二個方法中,即getWallet2()並且在第二種方法中再次獲得1000,如上面代碼的註釋中所解釋的。

我試過用propagation,0 Isolation,Lock但我還是沒有得到所需的結果。

是否有處理此類之情況的解決方案,我無法找到一個解決方案,這樣的情況,這是我在一個巨大的貨幣交易系統,這裏的命中率時,出現了之情況的簡化版本每秒大約4到5次交易。

以上只是我試圖重現scenerio的一個例子,下面是實際的代碼。

@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; 
    } 

} 

並在同一實體操作的另一方法如下所示

@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); 

    } 

像這樣有七個方法,在不同的服務,在同一時間訪問錢包也可以有同時登錄的用戶數量以及概率是,用戶管理員也登錄並執行貨幣交易,這是我們遇到此問題的真實場景。

在此先感謝

+2

首先,沒有任何關於您的測試或交易的併發事件。我們對Spring AOP的工作原理缺乏瞭解,以及交易如何應用。簡而言之,代理被使用,只有方法調用INTO對象被代理。所以你從你的測試用例調用的方法的'@ Transactional'基本上是無用的(即使這個方法是'public')。測試真正的方法,而不是在你的測試案例中。緊接着你的服務層應該是事務層而不是存儲庫。 –

+0

謝謝Deinum,指出這些事情。這兩種方法實際上在服務層中。但事實是,我只是想重現這樣一個場景來理解。我想表達的是,有兩種服務使用存儲庫同時從數據庫訪問同一個實體。所以如果最後一個被提交,數據庫中更新的值。我會有興趣瞭解如何實現這一點,以便我可以處理併發事務。 –

+0

請提供一些鏈接或示例,我會很大 –

回答

4

你好,大家好我要回答我的問題,這可能幫助別人的未來,我已經找到了解決我的問題。感謝Denium指出這個問題。這真是一個很棒的概念。

我正在做的錯誤是內部調用方法,並在private方法上編寫@Transactional

@Transactional是使用spring AOP實現的,因此內部方法調用從未實際到達代理,並且@Transactional功能的行爲更爲奇怪。

因此,解決方案是將方法包裝在對象中,並在對象的方法上定義@Transactional,並僅對對象進行外部調用。

其他的解決方案可能會被定義我們自己point cutsadvice

更多參考,請訪問以下鏈接:

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

請隨意添加任何的建議和修改,

謝謝