我正在使用spring Data
。我对spring数据并发事务有问题,如下所示:
实体和存储库如下:
@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
是如下
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);
}
我正在同时对两个方法进行方法调用,如下所示:
@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;
}
问题是我没有在不同的方法调用中获得同一实体的更新值。
例如,如果ID为1的实体的值最初在调用方法getWallet1()之后具有1000的值,则该值应更新为1100,但不会在第二种方法即getWallet2()中得到反映,我又得到1000在上面的代码注释中解释的第二种方法中。
我试图用propagation
,Isolation
,Lock
但我仍然没有得到需要的结果。
是否有解决此类问题的解决方案,我无法找到这种情况的解决方案,这是我在大型货币交易系统中获得的Scenerio的简化版本,其点击率约为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);
}
像这样,在不同的服务中有7种方法可以同时访问电子钱包,因为可以同时有多个用户登录,并且有可能用户admin也已登录并执行货币交易,即真正的场景,我们得到了这个问题。
提前致谢
大家好,我将回答我自己的问题,这可能会在将来对某人有所帮助,我已经找到了解决问题的方法。感谢Denium指出问题所在。这确实是一个很棒的概念。
我在做错误正在对方法内部呼叫和写作@Transactional
的private
方法。
@Transactional
使用来实现,spring AOP
因此内部方法调用实际上不会到达代理,并且的功能行为也@Transactional
很混乱。
因此,解决方案是将方法包装在一个对象中并定义该对象的@Transactional
on方法,然后仅对该对象进行外部调用。
其他解决方案可能正在定义我们自己的解决方案point cuts
,advice
有关更多参考,请访问以下链接:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html https://www.mkyong.com/spring/spring-aop-example-pointcut-advisor/
请随时添加任何建议和修改,
谢谢
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句