如何在项目开发时,正确的使用锁和事务进行开发(将理论知识用到实际项目开发之中)

❤ 作者主页:李奕赫揍小邰的博客

❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~*

🍊 记得点赞、收藏、评论⭐️⭐️⭐️

📣 认真学习!!!🎉🎉

文章目录

今天再做接口系统中用户调用接口功能的时候,因为接口调用次数是有限制的,考虑到用户可能会瞬间调用大量接口次数,为了避免统计出错,需要涉及到事务和锁的知识。在这种情况下,如果我们是在分布式环境中运行的,那么可能需要使用分布式锁来保证数据的一致性。
我们平常可能背八股文,锁,事务等怎么使用,理论知识很了解,但当真正运用的时候可能会感觉无从下手。今天我就简单编写一下在正常环境和分布式环境应该怎么使用锁来确保数据的一致性和安全性。

初始代码

初始代码就是根据用户id和接口id进行判断是否为空,之后对总调用次数+1,剩余调用次数-1更新。

java 复制代码
public boolean invokeCount(long interfaceInfoId, long userId) {
      
        if (interfaceInfoId <= 0 || userId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 使用 UpdateWrapper 对象来构建更新条件
        UpdateWrapper<UserInterfaceInfo> updateWrapper = new UpdateWrapper<>();

        updateWrapper.eq("interfaceInfoId", interfaceInfoId);
        updateWrapper.eq("userId", userId);
      
        // leftNum=leftNum-1和totalNum=totalNum+1。意思是将leftNum字段减一,totalNum字段加一。
        updateWrapper.setSql("leftNum = leftNum - 1, totalNum = totalNum + 1");
        // 最后,调用update方法执行更新操作,并返回更新是否成功的结果
        return this.update(updateWrapper);
    }

正常环境下加锁

要确保在更新用户接口信息时的原子性、数据一致性和安全性,可以使用数据库事务和行级锁来实现。

java 复制代码
@Override
@Transactional  // 添加事务注解
public boolean invokeCount(long interfaceInfoId, long userId) {
    if (interfaceInfoId <= 0 || userId <= 0) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }

    // 开启数据库事务
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    TransactionStatus status = transactionManager.getTransaction(def);

    try {
        // 使用悲观锁来锁定要更新的记录,确保其他事务无法同时修改
        UserInterfaceInfo userInterfaceInfo = this.selectForUpdate(interfaceInfoId, userId);
        
        // 判断记录是否存在
        if (userInterfaceInfo == null) {
            throw new BusinessException(ErrorCode.RECORD_NOT_FOUND);
        }

        // 更新操作
        userInterfaceInfo.setLeftNum(userInterfaceInfo.getLeftNum() - 1);
        userInterfaceInfo.setTotalNum(userInterfaceInfo.getTotalNum() + 1);
        boolean updateResult = this.updateById(userInterfaceInfo);

        // 提交事务
        transactionManager.commit(status);

        return updateResult;
    } catch (Exception e) {
        // 发生异常时回滚事务
        transactionManager.rollback(status);
        throw e;
    }
}

private UserInterfaceInfo selectForUpdate(long interfaceInfoId, long userId) {
    // 使用forUpdate方法在查询时添加行级锁
    QueryWrapper<UserInterfaceInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("interfaceInfoId", interfaceInfoId);
    queryWrapper.eq("userId", userId);
    queryWrapper.forUpdate();  // 添加forUpdate方法
    return this.getOne(queryWrapper);
}

上述修改的关键点在于:

1.添加 @Transactional 注解:在方法上添加 @Transactional 注解,以确保方法的执行在一个数据库事务中。

2.使用事务管理器:根据具体的实际情况,注入适当的事务管理器,并使用它来开启、提交或回滚事务。在示例中,使用了 transactionManager。

3.添加行级锁:通过在查询时使用 forUpdate 方法,可以在查询过程中锁定要更新的记录,确保其他事务无法同时修改。

4.异常处理和事务回滚:在发生异常时,通过捕获异常并调用 transactionManager.rollback(status) 来回滚事务,以确保数据的一致性。

请注意,上述修改仅提供了一种实现思路,具体的实现方式可能因您的业务需求和数据库配置而有所不同。建议在实际应用中进行充分的测试和验证,以确保事务和锁的正确使用,以及数据的一致性和安全性。

分布式环境下加锁

在分布式项目中,为了保证数据的一致性和安全性,可以使用分布式锁来控制对用户接口信息的更新操作。

java 复制代码
@Override
public boolean invokeCount(long interfaceInfoId, long userId) {
    if (interfaceInfoId <= 0 || userId <= 0) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }

    // 获取分布式锁
    boolean lockAcquired = acquireDistributedLock(interfaceInfoId, userId);
    if (!lockAcquired) {
        throw new BusinessException(ErrorCode.LOCK_ACQUISITION_FAILED);
    }

    try {
        // 使用悲观锁来锁定要更新的记录,确保其他事务无法同时修改
        UserInterfaceInfo userInterfaceInfo = this.selectForUpdate(interfaceInfoId, userId);

        // 判断记录是否存在
        if (userInterfaceInfo == null) {
            throw new BusinessException(ErrorCode.RECORD_NOT_FOUND);
        }

        // 更新操作
        userInterfaceInfo.setLeftNum(userInterfaceInfo.getLeftNum() - 1);
        userInterfaceInfo.setTotalNum(userInterfaceInfo.getTotalNum() + 1);
        boolean updateResult = this.updateById(userInterfaceInfo);

        return updateResult;
    } finally {
        // 释放分布式锁
        releaseDistributedLock(interfaceInfoId, userId);
    }
}

private boolean acquireDistributedLock(long interfaceInfoId, long userId) {
    // 在这里实现获取分布式锁的逻辑,可以使用分布式锁框架(例如Redisson、ZooKeeper等)来实现。
    // 获取成功返回true,获取失败返回false。
    // 根据实际情况进行实现。
    // 示例:
    // return distributedLock.acquireLock(interfaceInfoId + "_" + userId);
    // 其中,distributedLock是分布式锁框架的实例。
}

private void releaseDistributedLock(long interfaceInfoId, long userId) {
    // 在这里实现释放分布式锁的逻辑,与acquireDistributedLock方法对应。
    // 根据实际情况进行实现。
    // 示例:
    // distributedLock.releaseLock(interfaceInfoId + "_" + userId);
    // 其中,distributedLock是分布式锁框架的实例。
}

private UserInterfaceInfo selectForUpdate(long interfaceInfoId, long userId) {
    // 使用forUpdate方法在查询时添加行级锁
    QueryWrapper<UserInterfaceInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("interfaceInfoId", interfaceInfoId);
    queryWrapper.eq("userId", userId);
    queryWrapper.forUpdate();  // 添加forUpdate方法
    return this.getOne(queryWrapper);
}

上述修改的关键点在于:

1.获取分布式锁:在 invokeCount 方法中,通过调用 acquireDistributedLock 方法获取分布式锁,确保只有一个线程能够执行更新操作。

2.释放分布式锁:在 finally 块中,通过调用 releaseDistributedLock 方法释放分布式锁,以便其他线程可以获取锁并执行更新操作。

2.acquireDistributedLock 和 releaseDistributedLock 方法的实现:根据您使用的分布式锁框架(例如Redisson、ZooKeeper等),实现获取和释放分布式锁的具体逻辑。

3.上述修改同样仅是提供了一种实现思路,具体的实现方式可能因使用的分布式锁框架和环境配置而有所不同。建议根据实际情况进行适当的调整和测试,以确保分布式锁的正确使用,以及数据的一致性和安全性。


总结:

我们不仅要熟悉锁,事务的理论知识,更要能够把这些理论知识实现到实际开发当中,这样我们才能更加熟练的掌握这个知识。

相关推荐
小灰灰__6 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭9 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果30 分钟前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林35 分钟前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨1 小时前
El表达式和JSTL
java·el
duration~2 小时前
Maven随笔
java·maven
zmgst2 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD2 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式