在使用springboot时,如何在java中使方法原子化?

2exbekwf  于 2021-07-13  发布在  Java
关注(0)|答案(1)|浏览(433)

我有一个用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;
        }
    }
4ngedf3f

4ngedf3f1#

仅仅设置隔离级别是不够的。您应该使用乐观或悲观锁定来实现您想要的行为。在这个答案中可以找到关于这个策略的简短的描述。

相关问题