hibernate使用snowflake算法进行主键ID生成

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/github_38924695/article/details/79022701

项目中一般采用hibernate自带的主键生成策略 ,在分布式的高并发项目,可能会出现主键重复,所以采用twitter的开源项目snowflake算法进行主键生成。
SnowFlake的结构如下(每部分用-分开):
1位标志位 41位时间戳 5位机器+5位数据标志 12位计数器
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
41位时间戳是因为当前时间减去起始时间的时间戳刚好在2^41范围内
1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker 类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
加起来刚好64位,为一个Long型。
SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
基本上snowflake算法只要在不调整机器时间或者在特殊时间(如进行闰秒调整时期将秒数减一)不会出现重复id,且生成速度快。
代码部分:首先将snowflake中的官方demo进行适当调整,因为不需要那么多机器所以将结构调整为 1 43 3 3 14的结构。
SnowFlakeIdWorker类:

/**
 * Twitter_Snowflake官网demo格式<br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 1位标志位                    41位时间戳                                               5位机器+5位数据标志          12位计数器                               
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 41位时间戳是因为当前时间减去起始时间的时间戳刚好在2^41范围内
 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
 * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
 * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位,为一个Long型。<br>
 * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
 * 
 * 算法在的<<表示左移,左移后后面剩下部分会补0,并不是循环移位
 * 二进制转换运算中负数的二进制需要用补码表示
 * 补码 = 反码 + 1 ;   补码 = (原码 - 1)再取反码
 * 
 * 本代码对字段进行了调整,采用43位时间戳  3位机器标志、3位数据标志  14位
 */
public class SnowFlakeIdWorker {

    // ==============================Fields===========================================
    /** 开始时间截 (2018-01-01) 毫秒级时间戳 */
    private final long twepoch = 1514736000000L;

    /** 机器id所占的位数 */
    private final long workerIdBits = 3L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 3L;

    /** 
     * 支持的最大机器id,结果是7 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) 
     * -1L ^ (-1L << n)表示占n个bit的数字的最大值是多少。举个栗子:-1L ^ (-1L << 2)等于10进制的3 ,即二进制的11表示十进制3。
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 
     * 支持的最大数据标识id,结果是7 
     *  -1L 原码 1000 0001   原码是在符号位加标志  正数 0 负数1
     *     反码1111 1110   正数原码是其本身 负数是符号位不变其余位取反
     *     补码1111 1111   原码-1再取反
     *  -1L << 3 1111 1000   
     *  -1L ^ (1111 1000)   1111 1000 ^ 1111 1111 结果为7
     * */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /** 序列在id中占的位数 */
    private final long sequenceBits = 14L;

    /** 机器ID向左移14位 */
    private final long workerIdShift = sequenceBits;

    /** 数据标识id向左移17位(14+3) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /** 时间截向左移20位(3+3+14) 机器码+计数器 */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /** 生成序列的掩码,这里为16383  */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~7) */
    private long workerId;

    /** 数据中心ID(0~7) */
    private long datacenterId;

    /** 毫秒内序列(0~16383) */
    private long sequence = 0L;

    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;

    private static String wordid;
    private static String dataid;

    private static SnowFlakeIdWorker idWorker;

    //==============================Constructors=====================================
    /**
     * 构造函数
     * @param workerId 工作ID (0~7)
     * @param datacenterId 数据中心ID (0~7)
     */
    public SnowFlakeIdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**
     * 提供空构造用于创建对象调用方法 
     */
    public SnowFlakeIdWorker() {}

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            //序列号 = 上次序列号+1 与 生成序列的掩码
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        //上次生成ID的时间截(保存时间戳)
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID(或运算)
        return ((timestamp - twepoch) << timestampLeftShift) //当前时间戳-开始时间戳 左移22位相当于两个差的22位
                | (datacenterId << datacenterIdShift) //数据中心Id左移17位
                | (workerId << workerIdShift) // 机器id左移12位
                | sequence; //毫秒内序列
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================
    /** 测试 */
    public static void main(String[] args) {
        SnowFlakeIdWorker idWorker = new SnowFlakeIdWorker(1L, 1L);
        for (int i = 0; i < 1000; i++) {
            long id = idWorker.nextId();
            System.out.println(Long.toBinaryString(id));
            System.out.println(id);
        }
    }

    /**
     * 获取数据库中机器相关信息并返回对象
     * @return
     */
    public SnowFlakeIdWorker getSnowFlakeIdWorker(){
        //加载配置文件
        InputStream is = WeixinConfigUtils.class.getResourceAsStream("/snowflake.properties");
        Properties properties = new Properties();
        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
            throw new BasicRuntimeException("加载snowflake文件异常" + e.getMessage());
        }
        wordid = properties.getProperty("worid"); 
        dataid = properties.getProperty("dataid"); 
        //校验获取到参数后在进行加载
        if (StringUtils.isNotBlank(dataid) && StringUtils.isNotBlank(wordid)) {
            idWorker = new SnowFlakeIdWorker(Long.valueOf(dataid), Long.valueOf(dataid));
            return idWorker;
        }
        return null;
    }

    /**
     * 获取id
     */
    public Long getId(){

        SnowFlakeIdWorker snowFlakeIdWorker = getSnowFlakeIdWorker();
        if (snowFlakeIdWorker != null) {
            return snowFlakeIdWorker.nextId();
        }
        return null;
    }
}

配置文件:

##id生成器snowflake算法的配置文件
##机器号 范围 0-7
worid = 1
##数据源 用于区分机器上不同应用  范围 0-7
dataid = 1

hibernate自定义id生成器类:

import java.io.Serializable;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.id.IdentifierGenerator;

/**
 * 生成id的方法
 * @author yangfuren
 * @since 2018年01月10
 */
@Service
@Component
@Transactional
public class GenerateId implements IdentifierGenerator,Configurable{
    @Resource
    private SessionFactory sessionFactory;
    public String workid;

    public String dataid;

    public SnowFlakeIdWorker snowFlakeIdWorker;

    /**
     * hibernate自定义主键生成规则必须实现 IdentifierGenerator  generate 为默认方法
     */
    @Override
    public Serializable generate(SessionImplementor session, Object object)
            throws HibernateException {
        Long id = snowFlakeIdWorker.nextId();
        if (id != null) {
            return id;
        }else {
            return null;
        }
    }

    /**
     * 加载配置文件中的数据初始化snowflakeworker类
     */
    @Override
    public void configure(Type type, Properties properties, Dialect d)
            throws MappingException {
        //加载配置文件
        InputStream is = GenerateId.class.getResourceAsStream("/snowflake.properties");
        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
            throw new BasicRuntimeException(this,"加载snowflake文件异常" + e.getMessage());
        }
        workid = properties.getProperty("workid"); 
        dataid = properties.getProperty("dataid"); 
        if (StringUtils.isNotBlank(dataid) && StringUtils.isNotBlank(workid)) {
            snowFlakeIdWorker = new SnowFlakeIdWorker(Long.valueOf(workid), Long.valueOf(dataid));
        }
    }

    public String getWorkid() {
        return workid;
    }

    public void setWorkid(String workid) {
        this.workid = workid;
    }

    public String getDataid() {
        return dataid;
    }

    public void setDataid(String dataid) {
        this.dataid = dataid;
    }


}

测试实体类中配置:(这里注意主键的配置)

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.GenericGenerator;

@Entity
@Table(name = "test_obj")
public class TestObj implements Serializable{

    private static final long serialVersionUID = 1L;

    private Long id;

    private String name;

    **@Id
    @GeneratedValue(generator = "idGenerator")
    @GenericGenerator(name = "idGenerator", strategy = "com.wellness.platfront.common.util.GenerateId")**
    @Column(name = "scheme_model_id", nullable = false)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

数据库中测试结果:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/github_38924695/article/details/79022701