2016-08-22 6 views
5

spring Dataを使用しています。次のように春のデータでの同時トランザクションの処理

エンティティとリポジトリは、次のとおりです:次のように 私は春データ同時トランザクションに問題がある、私は作ってい

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

} 

を次のように

@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です次のように2つのメソッドを同時に呼び出すメソッド呼び出し:

@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に更新されますが、2番目のメソッドgetWallet2()には反映されません。上記のコードのコメントで説明したように、2番目のメソッドでは1000を取得します。

私はpropagationIsolationLockと試しましたが、依然として必要な結果が得られません。

このような状況を解決するソリューションはありますか?そのような状況に対する解決策を見つけることはできません。これは、ヒット率が高い巨額の金銭的取引システムになっています。 1秒あたり約4〜5トランザクション。

上記の例は、私がシーンを再現しようとしたばかりの例です。以下は同じものの実際のコードです。

@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がどのように動作するかについてのあなたの理解は欠けており、トランザクションの適用方法もありません。短いプロキシが使用され、オブジェクトへのメソッド呼び出しのみがプロキシされます。だから、あなたのテストケースから呼び出すメソッドの '@ Transactional'は、(メソッドが' public'であっても)基本的に役に立たないのです。あなたのテストケースではなく、実際の方法をテストしてください。それに続いて、サービスレイヤは、リポジトリではなくトランザクションレイヤでなければなりません。 –

+0

これらのことを指摘してくれたDeinumに感謝します。これらの2つのメソッドは実際にサービスレイヤーにあります。しかし、実際には、理解のためにそのような風景を再現したかっただけです。私が伝えたいことは、リポジトリを使ってデータベースから同じエンティティに同時にアクセスする2つのサービスがあることです。したがって、コミットする最後の値がデータベース内で更新される値。私は同時トランザクションを処理できるようにこれをどのように実装できるかを理解することに興味があります。 –

+0

いくつかのリンクや例を提供してください、私はgreatefulです –

答えて

4

こんにちはみんなは、私は私の問題への解決策を発見しました。問題を指摘してくれてありがとうDenium。それは本当に素晴らしいコンセプトです。

私がしていた間違いは、メソッドへの内部呼び出しを行い、メソッドで@Transactionalと書くことでした。

@Transactionalは、spring AOPを使用して実装されているため、内部メソッド呼び出しは実際にプロキシに到達することはなく、@Transactionalの動作はwierdです。

解決策は、オブジェクトにメソッドをラップし、オブジェクトのメソッドに@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/

を、任意の提案や編集を追加すること自由に感じ

してください

ありがとう

関連する問題