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を取得します。
私はpropagation
、Isolation
、Lock
と試しましたが、依然として必要な結果が得られません。
このような状況を解決するソリューションはありますか?そのような状況に対する解決策を見つけることはできません。これは、ヒット率が高い巨額の金銭的取引システムになっています。 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);
}
が存在することができると同時に、財布にアクセス異なるサービス七の方法がある以下に示します。同じ時間にログインしたユーザーの数と、ユーザー管理者がログインして通貨取引を実行する可能性があります。これは、この問題が発生する実際の状況です。事前に
おかげで私は、これは将来的に誰かを助けるかもしれない自分の質問に答えるつもりです
まず、テストやトランザクションに関しては、何も並行していません。 Spring AOPがどのように動作するかについてのあなたの理解は欠けており、トランザクションの適用方法もありません。短いプロキシが使用され、オブジェクトへのメソッド呼び出しのみがプロキシされます。だから、あなたのテストケースから呼び出すメソッドの '@ Transactional'は、(メソッドが' public'であっても)基本的に役に立たないのです。あなたのテストケースではなく、実際の方法をテストしてください。それに続いて、サービスレイヤは、リポジトリではなくトランザクションレイヤでなければなりません。 –
これらのことを指摘してくれたDeinumに感謝します。これらの2つのメソッドは実際にサービスレイヤーにあります。しかし、実際には、理解のためにそのような風景を再現したかっただけです。私が伝えたいことは、リポジトリを使ってデータベースから同じエンティティに同時にアクセスする2つのサービスがあることです。したがって、コミットする最後の値がデータベース内で更新される値。私は同時トランザクションを処理できるようにこれをどのように実装できるかを理解することに興味があります。 –
いくつかのリンクや例を提供してください、私はgreatefulです –