乐观锁转账

在Spring Boot和MyBatis中,我们可以使用@Transactional注解和乐观锁或悲观锁来实现转账扣款操作。以下是一个使用乐观锁的示例:

首先,我们需要在账户表中添加一个version字段,用于实现乐观锁。

然后,我们创建一个Account实体类:``

public class Account {
    private int id;
    private int balance;
    private int version; // 用于乐观锁

    // getters and setters...
}

接着,我们创建一个AccountMapper接口:

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

@Mapper
public interface AccountMapper {
    @Select("SELECT * FROM accounts WHERE id = #{id}")
    Account findById(@Param("id") int id);

    @Update("UPDATE accounts SET balance = balance - #{amount}, version = version + 1 WHERE id = #{id} AND version = #{version}")
    int withdraw(@Param("id") int id, @Param("amount") int amount, @Param("version") int version);

    @Update("UPDATE accounts SET balance = balance + #{amount} WHERE id = #{id}")
    void deposit(@Param("id") int id, @Param("amount") int amount);
}

最后,我们创建一个BankService类,实现转账扣款操作:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BankService {
    private final AccountMapper accountMapper;

    public BankService(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    @Transactional
    public void transfer(int fromId, int toId, int amount) {
        Account fromAccount = accountMapper.findById(fromId);
        if (fromAccount.getBalance() < amount) {
            throw new RuntimeException("Insufficient balance");
        }

        int updatedRows = accountMapper.withdraw(fromId, amount, fromAccount.getVersion());
        if (updatedRows == 0) {
            throw new RuntimeException("Concurrent modification detected");
        }

        accountMapper.deposit(toId, amount);
    }
}

在这个例子中,我们使用@Transactional注解来开启事务。在transfer方法中,我们首先检查余额是否足够,然后尝试扣款。如果扣款失败(可能是因为并发修改),我们就抛出一个异常,然后Spring会自动回滚事务。否则,我们就存款,然后Spring会自动提交事务。

注意,这只是一个简单的例子,实际的生产环境可能会更复杂。例如,你可能需要处理更多的异常情况,或者使用悲观锁来处理高并发环境。

在实际的生产环境中,如果扣款失败(可能是因为并发修改),我们通常会尝试重新执行操作,而不是直接抛出异常。这就是所谓的重试机制。

以下是一个使用重试机制的转账扣款操作示例:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BankService {
    private final AccountMapper accountMapper;

    public BankService(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    @Transactional
    public void transfer(int fromId, int toId, int amount) {
        for (int i = 0; i < 3; i++) { // 尝试3次
            Account fromAccount = accountMapper.findById(fromId);
            if (fromAccount.getBalance() < amount) {
                throw new RuntimeException("Insufficient balance");
            }

            int updatedRows = accountMapper.withdraw(fromId, amount, fromAccount.getVersion());
            if (updatedRows > 0) {
                accountMapper.deposit(toId, amount);
                return;
            }
        }

        throw new RuntimeException("Concurrent modification detected");
    }
}

在上述的转账代码中,我们可以进行以下几点优化:

  1. 异常处理:在实际的生产环境中,我们需要处理更多的异常情况,例如数据库连接失败、SQL语句错误等。我们可以定义一个自定义的异常类,用于封装这些异常情况。

  2. 日志记录:我们可以添加日志记录,用于记录转账操作的详细信息,例如转账金额、转账时间、转账结果等。这对于问题排查和系统监控非常有用。

  3. 参数校验:我们需要校验输入参数的有效性,例如账户ID是否存在、转账金额是否大于0等。

以下是一个优化后的转账示例:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class BankService {
    private final AccountMapper accountMapper;
    private static final Logger logger = LoggerFactory.getLogger(BankService.class);

    public BankService(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    @Transactional
    public void transfer(int fromId, int toId, int amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be greater than zero");
        }

        for (int i = 0; i < 3; i++) { // 尝试3次
            Account fromAccount = accountMapper.findById(fromId);
            if (fromAccount == null) {
                throw new IllegalArgumentException("Account " + fromId + " does not exist");
            }

            if (fromAccount.getBalance() < amount) {
                throw new RuntimeException("Insufficient balance");
            }

            int updatedRows = accountMapper.withdraw(fromId, amount, fromAccount.getVersion());
            if (updatedRows > 0) {
                accountMapper.deposit(toId, amount);
                logger.info("Transfer successful: {} -> {}, amount: {}", fromId, toId, amount);
                return;
            }

            logger.warn("Concurrent modification detected, retrying...");
        }

        logger.error("Transfer failed after 3 attempts: {} -> {}, amount: {}", fromId, toId, amount);
        throw new RuntimeException("Concurrent modification detected");
    }
}

在这个例子中,我们添加了参数校验、日志记录,并且在发生并发修改时,我们会记录一个警告日志,然后重试操作。如果尝试了3次还是失败,我们就记录一个错误日志,然后抛出一个异常。

在Spring Boot和MyBatis中,我们可以使用@Transactional注解和悲观锁来实现转账扣款操作。以下是一个使用悲观锁的示例:

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

@Mapper
public interface AccountMapper {
    @Select("SELECT * FROM accounts WHERE id = #{id} FOR UPDATE")
    Account findByIdForUpdate(@Param("id") int id);

    @Update("UPDATE accounts SET balance = balance - ? WHERE id = ?")
    int withdraw(@Param("id") int id, @Param("amount") int amount);

    @Update("UPDATE accounts SET balance = balance + ? WHERE id = ?")
    void deposit(@Param("id") int id, @Param("amount") int amount);
}
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BankService {
    private final AccountMapper accountMapper;

    public BankService(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    @Transactional
    public void transfer(int fromId, int toId, int amount) {
        Account fromAccount = accountMapper.findByIdForUpdate(fromId);
        if (fromAccount.getBalance() < amount) {
            throw new RuntimeException("Insufficient balance");
        }

        accountMapper.withdraw(fromId, amount);
        accountMapper.deposit(toId, amount);
    }
}

在这个例子中,我们使用@Transactional注解来开启事务。在transfer方法中,我们首先获取fromId账户的信息,并对该账户加锁(悲观锁)。然后,我们检查余额是否足够,如果足够,就执行扣款和存款操作。因为我们使用了悲观锁,所以在我们操作fromId账户的过程中,其他事务不能修改该账户,这就保证了转账操作的一致性。

注意,这只是一个简单的例子,实际的生产环境可能会更复杂。例如,你可能需要处理更多的异常情况,或者使用乐观锁来处理高并发环境。

为了避免死锁,一种常见的策略是总是按照相同的顺序锁定资源。例如,我们可以按照账户ID的顺序来锁定账户。这样,上述的情况就不会发生,因为两个事务会按照相同的顺序来锁定账户A和账户B,不会形成死锁。这句话能对上述代码优化实现下吗

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BankService {
    private final AccountMapper accountMapper;

    public BankService(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    @Transactional
    public void transfer(int fromId, int toId, int amount) {
        // 按照账户ID的顺序来锁定账户
        int minId = Math.min(fromId, toId);
        int maxId = Math.max(fromId, toId);

        Account minAccount = accountMapper.findByIdForUpdate(minId);
        if (minId == fromId && minAccount.getBalance() < amount) {
            throw new RuntimeException("Insufficient balance");
        }

        Account maxAccount = accountMapper.findByIdForUpdate(maxId);
        if (maxId == fromId && maxAccount.getBalance() < amount) {
            throw new RuntimeException("Insufficient balance");
        }

        if (minId == fromId) {
            accountMapper.withdraw(fromId, amount);
            accountMapper.deposit(toId, amount);
        } else {
            accountMapper.withdraw(toId, amount);
            accountMapper.deposit(fromId, amount);
        }
    }
}

在实际的生产环境中,数据库通常会提供死锁检测和解决的机制。例如,如果数据库检测到死锁,它可能会自动终止其中一个事务,从而解除死锁。然后,应用程序可以捕获到这个异常,然后重新尝试事务。这句话代码实现下

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.dao.DeadlockLoserDataAccessException;

@Service
public class BankService {
    private final AccountMapper accountMapper;

    public BankService(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    public void transfer(int fromId, int toId, int amount) {
        while (true) {
            try {
                doTransfer(fromId, toId, amount);
                break; // 如果转账成功,跳出循环
            } catch (DeadlockLoserDataAccessException e) {
                // 如果发生死锁,重新尝试转账
            }
        }
    }

    @Transactional
    protected void doTransfer(int fromId, int toId, int amount) {
        // 按照账户ID的顺序来锁定账户
        int minId = Math.min(fromId, toId);
        int maxId = Math.max(fromId, toId);

        Account minAccount = accountMapper.findByIdForUpdate(minId);
        if (minId == fromId && minAccount.getBalance() < amount) {
            throw new RuntimeException("Insufficient balance");
        }

        Account maxAccount = accountMapper.findByIdForUpdate(maxId);
        if (maxId == fromId && maxAccount.getBalance() < amount) {
            throw new RuntimeException("Insufficient balance");
        }

        if (minId == fromId) {
            accountMapper.withdraw(fromId, amount);
            accountMapper.deposit(toId, amount);
        } else {
            accountMapper.withdraw(toId, amount);
            accountMapper.deposit(fromId, amount);
        }
    }
}

参考http://t.csdn.cn/PCBmA参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值