在分布式项目中,数据表的主键id一般可能存在于uuid或自增id这两种形式,uuid好理解而且实现起来也最容易,但是缺点就是数据表中的主键id是32位的字符串,在大数据查询等情况下性能会相对比较差,所以在需求允许的情况下,我们通常会优先考虑使用自增id来代替uuid使用。
在分布式项目中如果你的数据表的主键id是自增id,那么常见的生成对象主键id的方式有:
雪花算法
优点:实现简单
缺点:生成id较长、生成id不连续,会造成id浪费
采用框架自带的id生成器,如mybatisplus的@autoid
优点:依赖框架,实现简单
缺点:无法在插入对象前获取到对象的主键id
采用redis缓存生成主键id等
优点:生成的id连续,数据在表中的可读性好
缺点:借助redis缓存,实现相比前两种较复杂
这篇文章我们主要介绍如何通过redis来实现生成对象自增id的方法。
1、缓存实现原理
通过redis实现对象自增id的方式,主要是通过redis的incr
和 incrby
命令来对键值进行递增操作,从而实现计数器的功能,主要原因是redis 是单线程模型,所有命令都是原子操作,不会发生竞态条件,
在使用时要留意以下特点与注意事项
原子性:
incr
、incrby
和incrbyfloat
命令都是原子性的,这意味着如果多个客户端同时执行这些命令,redis 会保证每个命令的执行不会发生竞态条件。数据类型要求:这些命令要求操作的值是整数(
incr
和incrby
)或浮点数(incrbyfloat
)。如果键对应的值不是数值类型,redis 会返回错误。性能:redis 是单线程的,所有命令都是原子操作,因此在高并发环境下执行这些命令时,性能表现非常好。
键不存在的情况:如果执行
incr
或incrby
时,键不存在,redis 会自动创建这个键并初始化其值为 0,然后进行递增。
2、redis工具类实现id自动生成
了解到通过redis实现的原理之后,就是如何设计缓存以保证每张表的数据之间不会相互影响,
以user表为例,对应的model为usermodel,那么我们就可以将model名称作为key,当前已有的最大的id作为value来进行存储,因为每一张表对应的model名称都是不一样的,所以这里一定不会出现key重复的情况,
下面是一些在redis中生成和获取对象自增id的工具方法,方便新建对象时直接调用
@component("redisutils") @requiredargsconstructor @slf4j public class redisutils implements initializingbean { private final stringredistemplate stringredistemplate; private static redisutils redisutils; @override public void afterpropertiesset() { redisutils = this; } /** * 获取自增id */ public static <t> integer getincr(class<t> tclass) { try { string key = tclass.getname(); long increment = redisutils.stringredistemplate.opsforvalue().increment(key, 1); return math.tointexact(increment); } catch (exception e) { e.printstacktrace(); throw new redissystemexception(e.getmessage(), e); } } /** * 获取自增id,指定递增长度,用于批量创建对象时,只请求一次redis * 如批量创建五个对象,delta等于5 */ public static <t> integer getincr(class<t> tclass, int delta) { try { string key = tclass.getname(); long increment = redisutils.stringredistemplate.opsforvalue().increment(key, delta); return math.tointexact(increment); } catch (exception e) { e.printstacktrace(); throw new redissystemexception(e.getmessage(), e); } } /** * 缓存预热,用于项目启动时初始化redis中各个表的最大id */ public static <t> long incrpreheat(class<t> tclass, long maxvalue) { if (maxvalue == null) { maxvalue = 0l; } if (maxvalue < 0) { //异常抛出,预热的初始值不能小于0 } integer incr = getincr(tclass); if (maxvalue <= incr) { return incr; } return getincr(tclass, (int) (maxvalue - incr)); } }
解释:initializingbean是spring提供的拓展性接口,initializingbean接口为bean提供了属性初始化后的处理方法,它只有一个afterpropertiesset方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
3、缓存预热
缓存预热是指在项目启动时,将每一张表当前最大的主键id预先加载到redis中,这样在后面使用的时候,就可以直接从redis中获取下一次id即可,不需要再去访问数据库查询最大id,缓存预热一般会建立在redis专门的初始化类中,以便在启动项目时可以运行该类,具体如下:
/** * 缓存预热 * * @author hxy */ @component @dependson("redisutils") @requiredargsconstructor public class redisinit { private final usermapper usermapper; /** * 获取每一张表中当前的最大id,然后预热到redis中 */ @postconstruct public void init() { redisutils.incrpreheat(usermodel.class, usermapper.maxid()); } }
解释:@dependson注解可以定义在类和方法上,意思是我这个组件要依赖于另一个组件,也就是说被依赖的组件会比该组件先注册到ioc容器中。
解释:@postconstruct注解
在java中,@postconstruct注解,通常用于标记一个方法,它表示该方法在类实例化之后(通过构造函数创建对象之后)立即执行。
加上@postconstruct注解的方法会在对象的所有依赖项都已经注入完成之后执行。通过使用@postconstruct注解,我们可以确保在对象完全创建和初始化之后才执行这些操作。这个注解通常用在依赖注入(dependency injection)的框架中,例如spring。
@postconstruct 注解可以用在任何类的方法上,但它最常用于标记在 spring framework 中的 bean 类中的初始化方法。
4、model层封装调用
在实例化一个model对象的时候,要将model的主键id进行赋值,这个时候就要从redis中获取到当前对象应该对应的主键id,我们可以在model类中构建一个newinstance方法或有参构造,来专门的生成需要赋值主键id的对象,在方法中调用redis工具类的getincr方法,获取到最新最小未使用的主键id并赋值给新建的model即可。
@getter @setter public class usermodel { private integer id; private string name; private integer age; private string tel; public usermodel() { } /** * 提供一个能够生成自增id的有参构造,需要生成自增id时调用 */ public usermodel(boolean autoid) { if (autoid) { this.id = redisutils.getincr(usermodel.class); } } }
如果你是需要批量创建对象并且给对象赋值主键id的情况,可以按照如下方式使用,这样可以只调用一次redis,避免重复调用redis影响性能。
@service @requiredargsconstructor public class userserviceimpl { private final usermapper usermapper; public void batchadduser(){ int addnumber = 10; //提前生成自定跨度的id,比如之前最大id是6,获取后得到的incr是16 integer incr = redisutils.getincr(usermodel.class, addnumber); list<usermodel> usermodels = new arraylist<>(); for (int i = 0; i < addnumber; i++) { usermodel usermodel = new usermodel(); //每一个主键id依次递减使用 usermodel.setid(incr--); usermodels.add(usermodel); } usermapper.insertbatch(usermodels); } }
至此,通过redis来获取对象自增id的方法已经完成,如果在使用过程中有其他场景需求,可以对redisutils中的方法进行拓展即可。
到此这篇关于使用redis实现生成对象自增id的方法的文章就介绍到这了,更多相关redis实现对象自增id内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论