执子之手

与子偕老


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索
close

MyBatis Plus中主键生成方式ASSIGN_ID的算法分析

时间: 2021-07-02   |   分类: 开发     |   阅读: 1914 字 ~4分钟   |   访问: 0

MyBatis Plus 中提供了 ASSIGN_ID 这种方式生成主键,使用起来非常方便,只要在PO上定义一下就可以了,例如:

1public class Order extends Model<Order> {
2
3    @TableId(value = "id", type = IdType.ASSIGN_ID)
4    private Long id;
5
6    //...
7}

这样在保存的时候,MyBatis Plus会负责生成主键值,自动设置到字段id中。 一直好奇这个主键是怎么生成的,抽时间查看了一下源码,特意记录一下。

1. MybatisDefaultParameterHandler

通过看MyBatis Plus的文档,发现这个主键生成过程应该是在ParameterHandler中进行处理的。在MyBatis Plus中ParameterHandler是用来设置参数规则的,当StatementHandler调用prepare方法之后,接下来就是调用它来进行设置参数。

对ASSIGN_ID进行处理的的地方是在MybatisDefaultParameterHandler中(在3.4.x版本中,该类改为MybatisParameterHandler),该类在包com.baomidou.mybatisplus.core中。对主键处理的代码在populateKeys这个方法中:

 1    /**
 2     * 填充主键
 3     *
 4     * @param tableInfo  数据库表反射信息
 5     * @param metaObject 元数据对象
 6     * @param entity     实体信息
 7     */
 8    protected static void populateKeys(TableInfo tableInfo, MetaObject metaObject, Object entity) {
 9        final IdType idType = tableInfo.getIdType();
10        final String keyProperty = tableInfo.getKeyProperty();
11        if (StringUtils.isNotBlank(keyProperty) && null != idType && idType.getKey() >= 3) {
12            final IdentifierGenerator identifierGenerator = GlobalConfigUtils.getGlobalConfig(tableInfo.getConfiguration()).getIdentifierGenerator();
13            Object idValue = metaObject.getValue(keyProperty);
14            // 此处判断是否idValue是否存在,不存在的话就要根据类型进行计算赋值
15            if (StringUtils.checkValNull(idValue)) {
16                if (idType.getKey() == IdType.ASSIGN_ID.getKey()) { // 此处处理ASSIGN_ID类型的主键生成算法
17                    if (Number.class.isAssignableFrom(tableInfo.getKeyType())) {
18                        // 此处调用IdentitifierGenerator生成主键
19                        metaObject.setValue(keyProperty, identifierGenerator.nextId(entity));
20                    } else {
21                        metaObject.setValue(keyProperty, identifierGenerator.nextId(entity).toString());
22                    }
23                } else if (idType.getKey() == IdType.ASSIGN_UUID.getKey()) { // 此处处理ASSIGN_UUID类型的主键生成算法
24                    metaObject.setValue(keyProperty, identifierGenerator.nextUUID(entity));
25                }
26            }
27        }
28    }

2. DefaultIdentifierGenerator

此类在包com.baomidou.mybatisplus.core.incrementer中。代码如下:

 1public class DefaultIdentifierGenerator implements IdentifierGenerator {
 2
 3    private final Sequence sequence;
 4
 5    public DefaultIdentifierGenerator() {
 6        this.sequence = new Sequence();
 7    }
 8
 9    public DefaultIdentifierGenerator(long workerId, long dataCenterId) {
10        this.sequence = new Sequence(workerId, dataCenterId);
11    }
12
13    public DefaultIdentifierGenerator(Sequence sequence) {
14        this.sequence = sequence;
15    }
16
17    @Override
18    public Long nextId(Object entity) {
19        // 此处调用Sequence的nextId算法生成真正的主键值
20        return sequence.nextId();
21    }
22}

3. Sequence

此类在包com.baomidou.mybatisplus.core.toolkit中。注意:以下代码进行了缩减。

  1public class Sequence {
  2    /**
  3     * 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
  4     */
  5    private final long twepoch = 1288834974657L;
  6    /**
  7     * 机器标识位数
  8     */
  9    private final long workerIdBits = 5L;
 10    private final long datacenterIdBits = 5L;
 11    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 12    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 13    /**
 14     * 毫秒内自增位
 15     */
 16    private final long sequenceBits = 12L;
 17    private final long workerIdShift = sequenceBits;
 18    private final long datacenterIdShift = sequenceBits + workerIdBits;
 19    /**
 20     * 时间戳左移动位
 21     */
 22    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 23    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 24
 25
 26    public Sequence() {
 27        this.datacenterId = getDatacenterId(maxDatacenterId);
 28        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
 29    }
 30
 31
 32    /**
 33     * 数据标识id部分
 34     */
 35    protected static long getDatacenterId(long maxDatacenterId) {
 36        long id = 0L;
 37        try {
 38            InetAddress ip = InetAddress.getLocalHost();
 39            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
 40            if (network == null) {
 41                id = 1L;
 42            } else {
 43                byte[] mac = network.getHardwareAddress();
 44                if (null != mac) {
 45                    // mac地址最后两节,例如对Mac地址00:15:5d:2d:0b:01,下面的结果是id=0x0b01
 46                    id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
 47                    // 这里是计算除以32后得到的余数
 48                    id = id % (maxDatacenterId + 1);
 49                }
 50            }
 51        } catch (Exception e) {
 52            logger.warn(" getDatacenterId: " + e.getMessage());
 53        }
 54        return id;
 55    }
 56    
 57        /**
 58     * 获取 maxWorkerId
 59     */
 60    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
 61        StringBuilder mpid = new StringBuilder();
 62        mpid.append(datacenterId);
 63        // 此处返回的是the name representing the running Java virtual machine
 64        String name = ManagementFactory.getRuntimeMXBean().getName();
 65        if (StringUtils.isNotBlank(name)) {
 66            /*
 67             * GET jvmPid
 68             */
 69            mpid.append(name.split(StringPool.AT)[0]);
 70        }
 71        /*
 72         * MAC + PID 的 hashcode 获取16个低位
 73         */
 74        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
 75    }
 76    
 77    /**
 78     * 获取下一个 ID
 79     *
 80     * @return 下一个 ID
 81     */
 82    public synchronized long nextId() {
 83        long timestamp = timeGen();
 84        //闰秒
 85        if (timestamp < lastTimestamp) {
 86            long offset = lastTimestamp - timestamp;
 87            if (offset <= 5) {
 88                try {
 89                    wait(offset << 1);
 90                    timestamp = timeGen();
 91                    if (timestamp < lastTimestamp) {
 92                        throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
 93                    }
 94                } catch (Exception e) {
 95                    throw new RuntimeException(e);
 96                }
 97            } else {
 98                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
 99            }
100        }
101
102        if (lastTimestamp == timestamp) {
103            // 相同毫秒内,序列号自增
104            sequence = (sequence + 1) & sequenceMask;
105            if (sequence == 0) {
106                // 同一毫秒的序列数已经达到最大
107                timestamp = tilNextMillis(lastTimestamp);
108            }
109        } else {
110            // 不同毫秒内,序列号置为 1 - 3 随机数
111            sequence = ThreadLocalRandom.current().nextLong(1, 3);
112        }
113
114        lastTimestamp = timestamp;
115
116        // 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分
117        return ((timestamp - twepoch) << timestampLeftShift)
118            | (datacenterId << datacenterIdShift)
119            | (workerId << workerIdShift)
120            | sequence;
121    }    
122}

跟踪到这里就可以看到具体的算法了。具体来说,该算法就是Twitter的雪花算法的变种。最后生成的主键值是一个64位长整数,其具体定义如下:

  • 最高1位未用,总为0,这样能够保证生成的主键值永远为整数;
  • 然后是41位时间戳,其值为UTC毫秒数减去一个固定值:1288834974657L;
  • 然后是5位datacenterId,从上面的算法来看是从主机的mac地址后2段计算出来的;
  • 紧接着是5位的workerId;
  • 最后是12位顺序号。也就是一毫秒时间内最多能够生成4096个主键值。

与Twitter的雪花算法的唯一区别是其41位的时间戳值多减去了一个固定值:1288834974657L。

#Spring# #Java# #MyBatis#
MySQL如何保存emoji字符
Linux常用命令介绍 05 - unzip
  • 文章目录
  • 站点概览
Orchidflower

Orchidflower

Do one thing at a time, and do well.

76 日志
6 分类
83 标签
GitHub 知乎 OSC 豆瓣
  • 1. MybatisDefaultParameterHandler
  • 2. DefaultIdentifierGenerator
  • 3. Sequence
© 2009 - 2022 执子之手
Powered by - Hugo v0.104.3
Theme by - NexT
ICP - 鲁ICP备17006463号-1
0%