2016-08-22 12 views
5

Używam spring Data. Mam problem z danymi wiosenne jednoczesnych transakcji w sposób następujący:Obsługa równoległych transakcji w danych wiosennych

Podmiot ten oraz repozytoria są następujące:

@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 jest następujący

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

} 

jestem podejmowania wywołanie metody do dwóch metod jednocześnie, jak pokazano poniżej:

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

I obie metody są jak opisano poniżej

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

Problemem jest to, że ja nie jestem coraz aktualizowane wartości tego samego podmiotu w różnych wywołań metod.

na przykład jeśli wartość jednostki o id 1 ma wartość 1000 początkowo po wywołaniu metody getWallet1() wartość powinna zostać zaktualizowana do 1100, ale nie zostanie odzwierciedlona w drugiej metodzie tj. GetWallet2() i ponownie otrzymuję 1000 w drugiej metodzie, jak wyjaśniono w komentarzach do kodu powyżej.

Próbowałem z propagation, Isolation, Lock, ale nadal nie dostałem wymaganych wyników.

Czy istnieje rozwiązanie, które poradzi sobie z takim scenerio, nie jestem w stanie znaleźć rozwiązania takiej sytuacji, Jest to uproszczona wersja scenerio, którą dostaję w ogromnym systemie transakcji pieniężnych, w którym wskaźnik trafień jest około 4 do 5 transakcji na sekundę.

Powyższy przykład to tylko przykład, w którym próbowałem odtworzyć scenerio, Poniżej znajduje się rzeczywisty kod dla tego samego.

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

} 

I kolejna metoda, która działa na tej samej jednostki jest pokazany poniżej

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

    } 

Jak to jest siedem metod w różnych usług, otwórz portfel w tym samym czasie, nie może być liczba użytkowników zalogowanych w tym samym czasie, a prawdopodobieństwo, że administrator użytkowników jest również zalogowany i wykonuje transakcje pieniężne, to prawdziwe scenerio, w którym pojawia się ten problem.

góry dzięki

+2

Po pierwsze, nie ma nic równoczesnego w teście ani transakcji. Brakuje ci wiedzy na temat działania Spring AOP i tego, w jaki sposób są stosowane transakcje. W krótkich proxy są używane i tylko wywołania metody do obiektu są proxy. Więc twoje '@ Transactional' w metodzie, którą wywołujesz ze swojej testówki, jest w zasadzie bezużyteczne (nawet jeśli metoda byłaby' public'). Przetestuj prawdziwą metodę, a nie jakąś w teście. Poza tym twoja warstwa usług powinna być warstwą transakcyjną, a nie repozytorium. –

+0

Dzięki Deinum, za wskazanie tych rzeczy. Te dwie metody znajdują się w warstwie usługi. Ale faktem jest, że chciałem odtworzyć takie scenerio dla zrozumienia. Co miałem zamiar przekazać to dwie usługi, które jednocześnie uzyskują dostęp do tej samej jednostki z bazy danych za pomocą repozytorium. Tak więc wartość, która jest aktualizowana w bazie danych, jeśli ostatnia została zatwierdzona. Byłbym zainteresowany zrozumieniem, jak można to zaimplementować, aby móc obsługiwać równoczesne transakcje. –

+0

Proszę podać kilka linków lub przykładów, byłbym wdzięczny –

Odpowiedz

4

Witam mam zamiar odpowiedzieć na moje własne pytanie to może komuś pomóc w przyszłości, znalazłem rozwiązanie mojego problemu. Dziękuję Denium za wskazanie problemu. To naprawdę świetna koncepcja.

Błąd, który robiłem, polegał na nawiązywaniu wewnętrznych połączeń z metodami i pisaniu @Transactional metodami private.

@Transactional jest zaimplementowane przy użyciu spring AOP, więc wewnętrzne wywołania metody nigdy nie docierają do proxy, a zachowanie funkcji @Transactional jest dziwne.

Rozwiązaniem było więc owinięcie metod w obiekt i zdefiniowanie @Transactional dotyczących metod obiektu i tylko wykonywanie zewnętrznych wywołań do obiektu.

Inne rozwiązania mogą być zdefiniowanie nasz własny point cuts i advice

Dalsze odniesienia można znaleźć na następujących stronach:

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

Prosimy dodać jakieś sugestie i zmiany,

Dzięki

Powiązane problemy