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

news/2024/6/29 12:11:50 标签: java, , 事务, 数据一致性与安全性

❤ 作者主页:李奕赫揍小邰的博客
❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~*
🍊 记得点赞、收藏、评论⭐️⭐️⭐️
📣 认真学习!!!🎉🎉

文章目录

  • 初始代码
  • 正常环境下加
  • 分布式环境下加
  • 总结:

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

初始代码

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

 


总结:

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


http://www.niftyadmin.cn/n/5348901.html

相关文章

淘宝API接口调用:案例分析与最佳实践(续)

进阶应用探讨 应用1&#xff1a;多渠道同步 在多渠道经营中&#xff0c;商家需要在不同的平台之间同步商品信息、库存和订单状态。利用淘宝API&#xff0c;商家可以自动化这一过程&#xff0c;减少人工操作的错误率和时间成本。例如&#xff0c;当一个商品在淘宝店铺售出时&a…

【GitHub项目推荐--游戏模拟器(switch)】【转载】

01 任天堂模拟器 yuzu 是 GitHub 上斩获 Star 最多的开源 Nintendo Switch 模拟器 &#xff0c;使用 C 编写&#xff0c;考虑到了可移植性&#xff0c;该模拟器包括 Windows 和 Linux 端。 如果你的 PC 满足必要的硬件要求&#xff0c;该模拟器就能够运行大多数商业游戏&…

C#,恩廷格尔组合数(Entringer Number)的算法与源程序

恩廷格尔组合数&#xff08;Entringer Number&#xff09;组合数学的序列数字之一。 E&#xff08;n&#xff0c;k&#xff09;是{1&#xff0c;2&#xff0c;…&#xff0c;n1}的排列数&#xff0c;从k1开始&#xff0c;先下降后上升。 计算结果&#xff1a; 源代码&#xf…

林浩然矩阵江湖历险记

林浩然矩阵江湖历险记 Lin Haoran’s Matrix Adventures 在那充满神秘色彩的矩阵世界里&#xff0c;林浩然面对的挑战是驯服一个具有六个个性元素的23矩阵——“小三儿”。这个矩阵由两行三列组成&#xff0c;每一个元素都像是棋盘上的一枚棋子&#xff0c;它们紧密排列在一起&…

qt 坦克大战游戏 GUI绘制

关于本章节中使用的图形绘制类&#xff0c;如QGraphicsView、QGraphicsScene等的详细使用说明请参见我的另一篇文章&#xff1a; 《图形绘制QGraphicsView、QGraphicsScene、QGraphicsItem、Qt GUI-CSDN博客》 本文将模仿坦克大战游戏&#xff0c;目前只绘制出一辆坦克&#…

CodeGPT--(Visual )

GitCode - 开发者的代码家园 gitcode.com/ inscode.csdn.net/liujiaping/java_1706242128563/edit?openFileMain.java&editTypelite marketplace.visualstudio.com/items?itemNameCSDN.csdn-codegpt&spm1018.2226.3001.9836&extra%5Butm_source%5Dvip_chatgpt_c…

NeRF:神经辐射场复杂场景的新视图合成技术

NeRF&#xff1a;神经辐射场复杂场景的新视图合成技术 NeRF&#xff1a;神经辐射场复杂场景的新视图合成技术项目背景与意义如何运行&#xff1f;快速开始更多数据集 预训练模型方法与实现结语服务 NeRF&#xff1a;神经辐射场复杂场景的新视图合成技术 在计算机视觉领域&…

执行rpm安装命令的时候抛出异常:rpmdb BDB0113 Thread/process

问题现象 错误&#xff1a;rpmdb: BDB0113 Thread/process 66126/140498505373504 failed: BDB1507 Thread died in Berkeley DB library 错误&#xff1a;db5 错误(-30973) 来自 dbenv->failchk&#xff1a;BDB0087 DB_RUNRECOVERY: Fatal error, run database recovery 错…