我有一个用springboot编写的rest应用程序,其中有一个钱包。钱包有如下方法 addAmount
, deductAmount
等等。代码如下:
Wallet控制器.java
public class WalletController {
public final LoadDatabase loadDatabase;
private final WalletRepository repository;
@Autowired
public WalletController(LoadDatabase loadDatabase, WalletRepository repository) {
this.loadDatabase = loadDatabase;
this.repository = repository;
}
@GetMapping("/addAmount")
@ResponseBody
public void addAmount(@RequestParam Long custId, @RequestParam Long amount){
try{
Wallet wallet = repository.findWalletsByCustId(custId).get(0);
wallet.balance = wallet.balance+amount;
repository.save(wallet);
}catch(IndexOutOfBoundsException e){
//handle exception
}
}
@GetMapping("/deductAmount")
@ResponseBody
public boolean deductAmount(@RequestParam Long custId, @RequestParam Long amount){
try{
Wallet wallet = repository.findWalletsByCustId(custId).get(0);
if(wallet.balance < amount)
return false;
wallet.balance = wallet.balance-amount;
repository.save(wallet);
return true;
}catch(IndexOutOfBoundsException e){
return false;
}
}
// some other methods.
这将是并发访问的,因此,我想使这两个 addAmount
以及 deductAmount
本质上是原子的。
为了验证这一点,我编写了一个shell脚本,它可以同时添加和减少一些数量。
钱包测试.sh
# ! /bin/sh
# Get the balance of Customer 201 before.
balanceBefore=$(curl -s "http://localhost:8082/getBalance?custId=201")
echo "Balance Before:" $balanceBefore
sh wa1 & sh wa2
wait
# Get the balance of Customer 201 afterwards.
balanceAfter=$(curl -s "http://localhost:8082/getBalance?custId=201")
echo "Balance After" $balanceAfter
哪里 wa1
以及 wa2
具体如下:
for i in {0..10};
do
# echo "Shell 1:" $i
resp=$(curl -s "http://localhost:8082/addAmount?custId=201&amount=100")
done
for i in {0..10};
do
# echo "Shell 2:" $i
resp=$(curl -s "http://localhost:8082/deductAmount?custId=201&amount=100")
done
由于并发访问,输出的形式如下:
Balance Before: 10000
Balance After 9900
Balance Before: 10000
Balance After 10300
Balance Before: 10000
Balance After 9600
我的预期产出是,前后的余额应该保持不变,即10000英镑。
现在,我读到了要使它原子化,我们可以利用 @Transactional
注解,我们可以将其添加到两个方法或整个类中。我两个都试过了,但是,我没有得到我想要的结果。
我在方法层添加了它,即
@GetMapping("/deductAmount")
@ResponseBody
@Transactional(isolation = Isolation.SERIALIZABLE)
public boolean deductAmount(@RequestParam Long custId, @RequestParam Long amount){
deductamount也一样,但不起作用。
我试着在课堂上添加它
@Controller
@Transactional(isolation = Isolation.SERIALIZABLE)
public class WalletController {
public final LoadDatabase loadDatabase;
private final WalletRepository repository;
这也不管用。
是 @Transactional
不是这样用的吗?我应该使用其他的锁定机制来完成我想要的吗?
编辑:
如前所述,我也尝试添加悲观锁。
import static javax.persistence.LockModeType.PESSIMISTIC_WRITE;
@PersistenceContext
private EntityManager em;
@GetMapping("/addAmount")
@ResponseBody
@Transactional
synchronized public void addAmount(@RequestParam Long custId, @RequestParam Long amount){
try{
Wallet wallet = repository.findWalletsByCustId(custId).get(0);
em.lock(wallet, PESSIMISTIC_WRITE);
wallet.balance = wallet.balance+amount;
repository.save(wallet);
}catch(IndexOutOfBoundsException e){
//handle exception
}
}
@GetMapping("/deductAmount")
@ResponseBody
@Transactional
synchronized public boolean deductAmount(@RequestParam Long custId, @RequestParam Long amount){
try{
Wallet wallet = repository.findWalletsByCustId(custId).get(0);
em.lock(wallet, PESSIMISTIC_WRITE);
if(wallet.balance < amount)
return false;
wallet.balance = wallet.balance-amount;
repository.save(wallet);
return true;
}catch(IndexOutOfBoundsException e){
return false;
}
}
1条答案
按热度按时间4ngedf3f1#
仅仅设置隔离级别是不够的。您应该使用乐观或悲观锁定来实现您想要的行为。在这个答案中可以找到关于这个策略的简短的描述。