用分布式锁和redis实现原子性递增,解决编号重复问题

2023-10-28


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.boot.starter.lock.client.RedissonLockClient;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.system.counter.entity.Counter;
import org.jeecg.modules.system.counter.mapper.CounterMapper;
import org.jeecg.modules.system.counter.service.ICounterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import javax.transaction.Transactional;
import java.util.Objects;

/**
 * @Description: 计数器
 * @Author: jiangfeng
 * @Date: 2022-07-03
 * @Version: V1.0
 */
@Service
@Slf4j
public class CounterServiceImpl extends ServiceImpl<CounterMapper, Counter> implements ICounterService {
    @Autowired
    private ICounterService counterService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    RedissonLockClient redissonLock;

    @Override
    public String tableCodeCounter(String key, int codeLength, String prefix) throws Exception{
        String finalCode;
        // 从 redis 中获取一个自增的计数器
        long incrementCounter = RedisAutoIncr(key);
        // 格式化编码长度,不包括前缀
        String formatCounter = String.format("%0" + codeLength + "d", incrementCounter);
        // 生成后的计数器
        if (StringUtils.isNotEmpty(prefix)) {
            finalCode = prefix + formatCounter;
        } else {
            finalCode = String.valueOf(incrementCounter);
        }
        // 保存至数据库
        boolean isSubmit = saveToTable(key, codeLength, prefix, incrementCounter);
        if (!isSubmit) {
            throw new Exception("数据库保存失败");
        }
        return finalCode;
    }

    @Transactional
    public boolean saveToTable(String key, int codeLength, String prefix, long counter){
        // 兼容数据库此处使用查询来判断数据库是否存在数据
        boolean submit;
        QueryWrapper<Counter> queryWrapper= new QueryWrapper<>();
        queryWrapper.eq("field_name", key);
        Counter countValue = counterService.getOne(queryWrapper);
        Counter entity = new Counter();
        if (countValue == null) {
            //不存在,新增
            entity.setFieldName(key);
            entity.setCodeLength(codeLength);
            entity.setPrefix(prefix);
            entity.setCounter(counter);
            submit = counterService.save(entity);
        } else {
            //存在,修改
            entity.setId(countValue.getId());
            entity.setCounter(counter);
            submit = counterService.updateById(entity);
        }
        return submit;
    }

    /**
     * @description 生成分布式唯一自增Id
     * @param key
     */
    public Long RedisAutoIncr(String key) { // 初始值是从1 开始 还是从0开始?
        // 加Redis分布式锁 双重保险,防止初始化的时候并发【不存在会有两种情况,一种是没有这个key的自增,一种是数据库中有,redis过期了或重启了】
        RedisAtomicLong redisAtomicLong;
        if (redisUtil.get(key) == null) {
            // 仅在key为空的时候加锁,不影响执行效率
            try {
                redissonLock.tryLock(key+ "_redisLock", -1, 3000);
                //查询是否在数据库中存在
                Counter counter = counterService.getOne(new QueryWrapper<Counter>().eq("field_name", key));
                if (counter!=null) {
                    // 从数据库赋值后,redis初始化不自增
                    redisUtil.set(key, counter.getCounter()+1);
                }
                redisAtomicLong = new RedisAtomicLong(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
                // redisAtomicLong.persist();
                // -1 为不过期
                log.warn(String.valueOf(redisAtomicLong.getExpire()));
                // 解锁
                redissonLock.unlock(key+ "_redisLock");
            } catch (Exception e){
                // 异常了,解锁
                redissonLock.unlock(key+ "_redisLock");
                throw e;
            }
        } else {
            // 如果存在key ,increment 是原子性的,不会重复计数
            redisAtomicLong = new RedisAtomicLong(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()) );
        }

        long Incr = redisAtomicLong.getAndIncrement();
        log.info("Incr------:" + Incr);
        return Incr;
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用分布式锁和redis实现原子性递增,解决编号重复问题 的相关文章

随机推荐

  • Mac下选择适合Unity的IDE

    以前都是Windows下使用Unity的 配合强大的visualstudio 写起代码来简直不要太爽 当我切换到Mac平台后 就感觉写代码的感受一下子回到了解放前 目前我尝试以下几种解决方案 终于让我写代码 1 vistualstudio
  • java.net.UnknownHostException完美解决。

    生产者会向Eureka注册 生产者如果部署在本机上 只需要部署以下参数即可 eureka instance instance id thisiswsgs01 设置服务描述 消费者只需要通过 http thisiswsgs01 user 1即
  • pubmed 影响因子_如何在Pubmed利用影响因子筛选文献?

    前几天给大家介绍了Pubmed上的2个神器 不知道的同学可以返回去看原文 两大神器 让你的PubMed飞起来 直接显示影响因子 Sci Hub链接 分区 用完之后是不是觉得方便了许多 今天 再给大家介绍一个使用Pubmed时候的小窍门 在P
  • UVA 401 Palindromes 题解

    Palindromes A regular palindrome is a string of numbers or letters that is the same forward as backward For example the
  • 制造企业有没有APS系统?生产车间如何计划排程和安排生产计划的?

    生产车间从组织职能上来说是以生产为重点的部门 生产计划应该由生产计划部来安排 如果从职能上来说 生产计划和生产车间放在一起会产生类似又当运动员又当裁判员的角色一样 从管理角度来看 生产计划和生产管理需要分开 原则上一个属于计划调度部门 一个
  • 记录一下实体类模型(Entity)的常用注解

    1 Data lombok注解 Data 注解的主要作用是提高代码的简洁 使用这个注解可以省去代码中大量的get set toString 等方法 2 TableName MybatisPlus注解 TableName注解主要是实现实体类型
  • 树莓派外设小开发(继电器、语音模块、超声波模块)

    wiringPi库安装与查看 首先要了解wiringPi库 wiringPi是一个很棒的树莓派IO控制库 使用C语言开发 提供了丰富的接口 GPIO控制 中断 多线程 等等 验证wiringPi的是否安装成功 输入gpio v 会在终端中输
  • 单片机C语言常用的头文件与库函数

    单片机C语言常用的头文件与库函数 以下头文件与库函数 只用头文件包含以下相应头文件即可调用 持续更新中 Intrins h头文件 函数名 函数原型 功能 返回值 crol unsigned char crol unsigned char v
  • 计算机密码输入正确,Win10输入正确密码却提示“密码不正确”如何解决

    在使用windows10系统过程中 很多用户都会遇到一些奇怪的问题 比如 有用户反馈开机登录win10的时候输入正确的登入密码 却碰到 密码不正确 请确认您的微软账户密码正确 的错误提示 无法登录 是怎么回事呢 在出现该问题的时候首先我们可
  • QT定时器的几种用法

    一 自定义定时器 定义一个定时器对象 绑定自定义槽函数 也可以定义定时器对象指针 这样可以一次性让多个定时器绑定同一个槽函数 头文件中加入 QTimer myTimer void myTimerEvent 自定义定时处理槽函数 在需要启动的
  • TCP的三次握手与四次挥手以及面试常见题

    TCP 是什么 TCP Transmission Control Protocol 传输控制协议 是一种面向连接的 可靠的 基于字节流的传输层通信协议 而且TCP是全双工模式 面向连接 你和你女朋友聊天是面向连接的 只有连接起来才可以通信的
  • 【Jmeter】什么是BeanShell?

    一 什么是BeanShell BeanShell是用Java写成的 一个小型的 免费的 可以下载的 嵌入式的Java源代码解释器 JMeter性能测试工具也充分接纳了BeanShell解释器 封装成了可配置的BeanShell前置和后置处理
  • 花生壳 linux客户端 命令

    phddns start service sshd status phddns status phddns version
  • HJ32密码截取

    有一定难度 难在考虑不周全 最后还是看了别人提到的方法 自己独立实现了一下 可能没有别人的简洁 但是易懂 调试的时候有些小毛病 但自己解决了 原因不是很清楚 最后总结里会提到 目标 在输入的字符串里找到对称的且是最长的那个字符串 思路 参考
  • 【Zabbix实战之部署篇】Zabbix使用SNMP监控Linux系统

    Zabbix实战之部署篇 Zabbix使用SNMP监控Linux系统 一 SNMP协议介绍 1 SNMP协议简介 2 SNMP协议特点 二 实践环境介绍 三 检查Zabbix监控平台环境 1 检查Zabbix相关组件容器状态 2 检查Zab
  • day05:js基础——函数、作用域问题

    js函数 作用域问题 概述 1 函数 1 1 函数概念 函数作用 函数构成 1 2 定义函数 调用函数 1 3 函数参数 1 4 函数返回值 1 5 js中的特殊函数 1 6 函数demo 2 变量作用域 3 js中的预解析 3 1 声明式
  • 用c语言制作一个简单的答题系统

    首先制作一个答题系统需要有一个题库 其次要有完整的出题系统 然后要能够进行答题和判断答案对错 最后就是统计答案正确率了 实现创建一个题库并不难 仅需要使用数组保存题目与标准答案就行了 使用strcpy函数将题目分别输入进题库 部分代码如下
  • Web自动化测试 —— 测试环境搭建 (Selenium+Python)及视频操作

    一 什么样项目适合做web自动化 1 软件需求不会频繁的变更 2 项目周期比较长 3 自动化的脚本能够重复利用 介入点 第一个版本的核心功能确认以后 系统测试 自动化的实施过程 1 可行性分析 2 框架的选择 selenum rf 框架的搭
  • 使用 maven 自动将源码打包并发布

    在pom xml中添加maven source plugin插件 maven生成 jar的同时生成 sources包
  • 用分布式锁和redis实现原子性递增,解决编号重复问题

    import com baomidou mybatisplus core conditions query QueryWrapper import lombok extern slf4j Slf4j import org apache co