Mybatis框架(9)---Mybatis自定义插件生成雪花ID作为表主键项目

#<center> Mybatis自定义插件生成雪花ID作为主键项目</center>html

先附上项目项目GitHub地址 spring-boot-mybatis-interceptorjava

有关Mybatis雪花ID主键插件前面写了两篇博客做为该项目落地的铺垫。git

一、Mybatis框架---Mybatis插件原理github

二、java算法---静态内部类实现雪花算法算法

该插件项目能够直接运用于实际开发中,做为分布式数据库表主键ID使用。spring

##<font color=#FFD700> 1、项目概述</font>sql

一、项目背景

在生成表主键ID时,咱们能够考虑主键自增 或者 UUID,但它们都有很明显的缺点数据库

主键自增一、自增ID容易被爬虫遍历数据。二、分表分库会有ID冲突。安全

UUID: 一、太长,而且有索引碎片,索引多占用空间的问题 二、无序。服务器

雪花算法就很适合在分布式场景下生成惟一ID,它既能够保证惟一又能够排序,该插件项目的原理是

经过拦截器拦截Mybatis的insert语句,经过自定义注解获取到主键,并为该主键赋值雪花ID,插入数据库中。

二、技术架构

项目整体技术选型

SpringBoot2.1.7 + Mybatis + Maven3.5.4 + Mysql + lombok(插件)

三、使用方式

在你须要作为主键的属性上添加@AutoId注解,那么经过插件能够自动为该属性赋值主键ID。

public class TabUser {
    /**
     * id(添加自定义注解)
     */
    @AutoId
    private Long id;
    /**
     * 姓名
     */
    private String name;
  //其它属性 包括get,set方法
}

四、项目测试

配置好数据库链接信息,直接启动Springboot启动类Application.java,访问localhost:8080/save-foreach-user就能够看到数据库数据已经有雪花ID了。

如图

<br>

<font color=#FFD700> 2、项目代码说明</font>

在正式环境中只要涉及到插入数据的操做都被该插件拦截,并发量会很大。因此该插件代码即要保证线程安全又要保证高可用。因此在代码设计上作一些说明。

一、线程安全

这里的线程安全主要是考虑产生雪花ID的时候必须是线程安全的,不能出现同一台服务器同一时刻出现了相同的雪花ID,这里是经过

静态内部类单例模式 + synchronized

来保证线程安全的,具体有关生成雪花ID的代码这里就不粘贴。

二、高可用

咱们去思考消耗性能比较大的地方可能出要出如今两个地方

1)雪花算法生成雪花ID的过程。
2)经过类的反射机制找到哪些属性带有@AutoId注解的过程。

第一点

其实在静态内部类实现雪花算法这篇博客已经简单测试过,生成20万条数据,大约在1.7秒能知足实际开发中咱们的须要。

第二点

这里是有比较好的解决方案的,能够经过两点去改善它。

1)、在插件中添加了一个Map处理器

/**
     * key值为Class对象 value能够理解成是该类带有AutoId注解的属性,只不过对属性封装了一层。
     * 它是很是可以提升性能的处理器 它的做用就是不用每一次一个对象经来都要看下它的哪些属性带有AutoId注解
     * 毕竟类的反射在性能上并不友好。只要key包含该Class,那么下次一样的class进来,就不须要检查它哪些属性带AutoId注解。
     */
    private Map<Class, List<Handler>> handlerMap = new ConcurrentHashMap<>();

插件部分源码

public class AutoIdInterceptor implements Interceptor {
   /**
     * Map处理器
     */
    private Map<Class, List<Handler>> handlerMap = new ConcurrentHashMap<>();
    /**
     * 某某方法
     */
    private void process(Object object) throws Throwable {
        Class handlerKey = object.getClass();
        List<Handler> handlerList = handlerMap.get(handlerKey);
        //先判断handlerMap是否已存在该class,不存在先找到该class有哪些属性带有@AutoId
        if (handlerList == null) {
                handlerMap.put(handlerKey, handlerList = new ArrayList<>());
                // 经过反射 获取带有AutoId注解的全部属性字段,并放入到handlerMap中
        }
         //为带有@AutoId赋值ID
        for (Handler handler : handlerList) {
            handler.accept(object);
        }
      }
    }

2)添加break label(标签)

这个就比较细节了,由于上面的process方法不是线程安全的,也就是说可能存在同一时刻有N个线程进入process方法,那么这里能够优化以下:

//添加了SYNC标签
        SYNC:
        if (handlerList == null) {
          //此时handlerList确实为null,进入这里
            synchronized (this) {
                handlerList = handlerMap.get(handlerKey);
          //但到这里发现它已经不是为null了,由于可能被其它线程往map中插入数据,那说明其实不须要在执行下面的逻辑了,直接跳出if体的SYNC标签位置。
          //那么也就不会执行 if (handlerList == null) {}里面的逻辑。
                if (handlerList != null) {
                    break SYNC;
                }
            }
        }

这里虽然很细节,但也是有必要的,毕竟这里并发量很大,这样设计能必定程度提高性能。

<br> <br>

我相信,不管从此的道路多么坎坷,只要抓住今天,早晚会在奋斗中尝到人生的甘甜。抓住人生中的一分一秒,赛过虚度中的一月一年!(7)

<br>

相关文章
相关标签/搜索