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
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. –
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. –
Proszę podać kilka linków lub przykładów, byłbym wdzięczny –