2016-08-22 14 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으로 업데이트해야하지만 두 번째 메서드 즉 getWallet2()에 반영되지 않습니다. 위 코드의 주석에서 설명한대로 두 번째 방법에서 1000을 얻습니다.

나는 propagation, Isolation, Lock으로 시도했지만 여전히 필요한 결과를 얻지 못했습니다.

이러한 상황을 해결할 수있는 솔루션이 있습니까? 이러한 상황에 대한 해결책을 찾을 수 없습니다. 이것은 거대한 금전 거래 시스템에 들어가는 단순한 버전의 시나리오입니다. 초당 약 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, 그 것들을 지적했다. 이 두 메서드는 실제로 서비스 계층에 있습니다. 그러나 사실은 이해를 돕기 위해 그러한 풍경을 재현하고 싶었습니다. 내가 전달하고자하는 것은 저장소를 사용하여 데이터베이스에서 동시에 동일한 엔티티에 액세스하는 두 가지 서비스가 있다는 것입니다. 마지막으로 커밋 될 경우 데이터베이스에서 업데이트되는 값입니다. 동시 트랜잭션을 처리 할 수 ​​있도록 이것이 어떻게 구현 될 수 있는지 이해하는 데 관심이 있습니다. –

+0

링크 또는 예제를 제공하십시오. greateful입니다. –

답변

4

안녕 얘들 아 내가이 미래에 다른 사람을 도움이 될 내 자신의 질문에 대답하기 위하여려고하고있다, 나는 내 문제에 대한 해결책을 발견했다. 문제를 지적 해 주신 Denium에게 감사드립니다. 정말 멋진 컨셉입니다.

내가 실수를하고있는 것은 실수로 메서드를 호출하고 메서드에 @Transactional을 쓰는 것이 었습니다.

@Transactionalspring AOP을 사용하여 구현되므로 내부 메서드 호출은 실제로 프록시에 도달하지 않으며 기능의 동작은 @Transactional입니다.

따라서 솔루션은 개체의 메서드를 래핑하고 개체의 메서드에 @Transactional을 정의하고 개체에 대한 외부 호출 만 수행하는 것이 었습니다.

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

을 제안하고 편집을 추가 주시기 바랍니다 :

다른 솔루션은 다음 링크를 참조하시기 바랍니다 더 참고 point cutsadvice

우리 자신을 정의 할 수 있습니다

감사합니다.