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);
}
아래 도시 동시에 로그인하는 사용자 수 및 사용자 관리자가 로그인하여 통화 거래를 수행 할 가능성이 있습니다. 즉,이 문제가 발생한 실제 장면입니다. 사전에
덕분에
처음에는 테스트 또는 트랜잭션과 관련하여 아무 것도 없습니다. Spring AOP의 작동 방식에 대한 이해와 트랜잭션 적용 방법에 대한 이해가 부족합니다. 짧은 프록시에서 사용되며 객체에 대한 메소드 호출 만 프록시됩니다. 그래서 당신이 테스트 케이스에서 호출하는 메소드에 대한'@ Transactional'은 기본적으로 쓸모가 없습니다 (메소드가 public이더라도). 테스트 케이스에서 확인하지 말고 실제 방법을 테스트하십시오. 그 다음으로는 서비스 계층이 저장소 계층이 아니라 트랜잭션 계층이어야합니다. –
감사의 말 Deinum, 그 것들을 지적했다. 이 두 메서드는 실제로 서비스 계층에 있습니다. 그러나 사실은 이해를 돕기 위해 그러한 풍경을 재현하고 싶었습니다. 내가 전달하고자하는 것은 저장소를 사용하여 데이터베이스에서 동시에 동일한 엔티티에 액세스하는 두 가지 서비스가 있다는 것입니다. 마지막으로 커밋 될 경우 데이터베이스에서 업데이트되는 값입니다. 동시 트랜잭션을 처리 할 수 있도록 이것이 어떻게 구현 될 수 있는지 이해하는 데 관심이 있습니다. –
링크 또는 예제를 제공하십시오. greateful입니다. –