[英]Potential concurrency issue while checking and updating a record in table
Here is the case, 就是这种情况
A member has to redeem a token to access (unlock) a given item. 成员必须兑换令牌才能访问(解锁)给定项目。 The relevant database tables are:
相关的数据库表是:
Table 1 表格1
Table MEMBER_BALANCE: MEMBER_ID, TOKEN_BALANCE
Table 2 表2
Table UNLOCKED_ITEM: MEMBER_ID, DATE_UNLOCKED, ITEM_ID
Checks, or constraints, that I need to enforce are 我需要执行的检查或约束是
My gut inclination is to write a simple method in MemberService.java: 我的直觉是在MemberService.java中编写一个简单的方法:
@Transactional
public void unlockItem(Member member, Item item){
memberBalanceDAO.decrementBalance(member);
itemDAO.unlockItem(member, item);
}
I've dealt with the second requirement by adding a unique
constraint on MEMBER_ID
/ ITEM_ID
pair on the UNLOCKED_ITEM
table. 我已经通过在
UNLOCKED_ITEM
表上的MEMBER_ID
/ ITEM_ID
对上添加unique
约束来处理第二个要求。
I think, the only thing I need to take care of would be, users trying to unlock many items at the same time, with TOKEN_BALANCE
requirement not met. 我认为,我唯一需要照顾的是,用户试图同时解锁许多物品,而未满足
TOKEN_BALANCE
要求。 For example, TOKEN_BALANCE
is 1, but the user clicks to unlock two items, virtually, at the same time. 例如,
TOKEN_BALANCE
为1,但是用户单击以同时虚拟地解锁两个项目。
Below is my MemberBalanceDAO.decrementBalance
method: 下面是我的
MemberBalanceDAO.decrementBalance
方法:
@Transactional
public void decrementBalance(Member member) {
MemberBalance memberBalance = this.findMemberBalance(member);
if (memberBalance.getTokens() >= 1) {
memberBalance.setTokens(memberBalance.getTokens() - 1);
this.save(memberBalance);
} else {
throw new SomeCustomRTException("No balance");
}
}
I don't think this protects me from the TOKEN_BALANCE
= 1 usecase. 我认为这不会保护我免受
TOKEN_BALANCE
= 1个用例的影响。 I'm worried about with multiple unlock requests at the same time. 我担心同时有多个解锁请求。 If the balance is 1, I could get two calls to
decrementBalance()
at the same time both committing the balance to 0, but then also two successful calls to itemDAO.unlockItem(...)
as well, right? 如果余额为1,那么我可以同时调用两次
decrementBalance()
并将余额都提交为0,但是也可以成功调用两次itemDAO.unlockItem(...)
,对吗?
How should I implement this? 我应该如何实施呢? Should I set the service level method's transaction to
isolation = Isolation.SERIALIZABLE
? 我应该将服务级别方法的事务设置
isolation = Isolation.SERIALIZABLE
吗? Or is there a cleaner/better way to approach this? 还是有一种更清洁/更好的方式来解决这个问题?
I would rather suggest you to introduce version
column in member_balance
table. 我宁愿建议您在
member_balance
表中引入version
列。 Refer to the docs, Optimistic Locking . 请参阅文档乐观锁定 。
As you mentioned that you couldn't modify the schema; 如前所述,您无法修改架构; you can go with versionless optimistic locks, explained here .
您可以使用无版本的乐观锁,如此处所述 。
Or you might like to go for pessimistic locking, explained here . 或者,您可能想要进行悲观锁定,如此处所述 。 Then, you can modify your method,
decrementBalance()
, to fetch the member balance there, don't use findMemberBalance()
. 然后,您可以修改方法
decrementBalance()
,以在那里获取成员余额,请不要使用findMemberBalance()
。 For example, 例如,
@Transactional
public void decrementBalance(Member member) {
MemberBalance memberBalance = entityManager.find(
MemberBalance.class, member.id, LockModeType.PESSIMISTIC_WRITE,
Collections.singletonMap( "javax.persistence.lock.timeout", 200 ) //If not supported, the Hibernate dialect ignores this query hint.
);
if (memberBalance.getTokens() >= 1) {
memberBalance.setTokens(memberBalance.getTokens() - 1);
this.save(memberBalance);
} else {
throw new SomeCustomRTException("No balance");
}
}
NB: It might not work as it is; 注意:它可能无法按原样工作; it's just to provide you some hints.
只是为您提供一些提示。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.