java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JAVA实现生成顺序ID

JAVA实现生成顺序ID,不浪费ID

作者:不秃头灬程序猿

这篇文章主要介绍了JAVA实现生成顺序ID,不浪费ID问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

JAVA实现生成顺序ID

 public Integer buildEmseId() {
        QueryWrapper<UseInfo> queryWrapper = new QueryWrapper<>();
        List<UseInfo> emseInfoList = emseInfoMapper.selectList(queryWrapper);
        List<Integer> list = new ArrayList<>();
        for (EmseInfo mad:emseInfoList) {
            list.add(mad.getEmseId());
        }
 
        int buildEmseId = 1;
        if(list.size()>0){
            boolean[] emseBoolean =new boolean[256];
            for (int emseId : list) {
                if (emseId >= emseBoolean.length || emseId < 0) {
                    continue;
                }
                emseBoolean[emseId] = true;
            }
            for(int i = 1; i < emseBoolean.length; i++){
                if(!emseBoolean[i]){
                    buildEmseId = i;
                    break;
                }
            }
            log.info("emseId=" + buildEmseId);
        }
        return buildEmseId;
    }
}

大概写了一下,之前有个需求是在存主键的时候不能浪费ID去存,比如数据库中id目前是1,2,3,5,6,很明显id是4的记录被删除了,正常我们新增一条会从7开始,但需求不能浪费id,此时新增必须是4。

但这种有个限制条件,id不是无限自增的,我这里最大是255.

这个方法会校验缺失的id并进行生成,如果没有缺失,就会按顺序生成。

一般可能没这种离谱需求。。

JAVA生成id方式

雪花算法生成id

启动类

(关于后端id为Long类型造成的精度丢失问题,前端用bigNumber的类型,可以处理长整数,也可避免此问题)

/**
     * 雪花算法.
     *
     * @param dataSource
     * @param environment
     * @return
     */
    @Bean
    public TwitterSnowflakeIdGenerator twitterSnowflakeIdGenerator(DataSource dataSource, Environment environment) {
        return TwitterSnowflakeIdGenerator.createInstance(dataSource, environment.getProperty("spring.application.name"));
    }

    /**
     * 转化long类型为String,处理前后端精度问题.
     * @return
     */
    @Bean("jackson2ObjectMapperBuilderCustomizer")
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance)
                .serializerByType(Long.TYPE, ToStringSerializer.instance);
    }

配置类

package com.mn.common.utils;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 基于Twitter/snowflake 算法的分布式全局id生成器 64位ID (42(毫秒)+10位(统一分配的业务号workerIdBits)+12(重复累加))
 * 
 * 整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分), 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
 * 
 * @author xuwei
 * @date 2021年9月27日
 * @version 1.0
 */
public final class TwitterSnowflakeIdGenerator {

    private final static long twepoch = 1288834974657L;
    // 机器标识位数
    private final static long workerIdBits = 10L;
    // 机器ID最大值
    private final static long maxWorkerId = ~(-1L << workerIdBits);

    // 毫秒内自增位
    private final static long sequenceBits = 12L;
    // 机器ID偏左移12位
    private final static long workerIdShift = sequenceBits;

    // 时间毫秒左移22位
    private final static long timestampLeftShift = sequenceBits + workerIdBits;

    private final static long sequenceMask = ~(-1L << sequenceBits);

    private long lastTimestamp = -1L;

    private long sequence = 0L;
    private final long workerId; // 此workId通过集中产生

    private static TwitterSnowflakeIdGenerator generator = null;

    /**
     * @param ds 包含sys_set的数据源
     * @param webDisplayName,web的上下文路径,即web.xml里面的displayName
     * @return
     */
    public static TwitterSnowflakeIdGenerator createInstance(DataSource ds, String webDisplayName) {
        if (generator == null) {
            synchronized (TwitterSnowflakeIdGenerator.class) {
                if (generator == null) {
                    long wId = getWorkerId(ds, webDisplayName);
                    if (wId == -1L)
                        return null;
                    generator = new TwitterSnowflakeIdGenerator(wId);
                }
            }
        }
        return generator;
    }

    private static long getWorkerId(DataSource ds, String webDisplayName) {
        long wId = -1L;
        try {
            // 获取主机名

            String hostname = NetworkUtil.getFirstIp();
            /*
             * InitialContext initialContext = new InitialContext(); DataSource ds =
             * (DataSource)initialContext.lookup(dbJndiName);
             */
            JdbcTemplate t = new JdbcTemplate(ds);
            // 主机名+web.xml里面的displayName,这个目前是唯一不变的,由它来决定唯一号,保存在SYS_SET表中
            // 命名规则为 主机名_displayName_id
            String key = hostname + "_" + webDisplayName + "_id";
            List<Map<String, Object>> list = t.queryForList("select svalue from sys_set where skey = ?", key);
            if (list.isEmpty()) {// 数据库没有这个记录
                // 这两个地方需要事务,暂不加
                List<Map<String, Object>> list2 = t.queryForList(
                        "select svalue from sys_set where status = 1 and class = ? order by sort", "ID_GENERATOR");
                boolean found = false;
                for (int i = 0; i < list2.size(); i++) {
                    Map<String, Object> map = list2.get(i);
                    if (map != null) {
                        int value = Integer.parseInt(map.get("svalue").toString());
                        if (value == i + 1) {
                            continue;
                        }
                        if (i < value) {// 找到一个小值,占用这个值
                            wId = i + 1;
                            t.update(
                                    "insert into sys_set(skey,sname,svalue,sort,status,class,remark,time_update) "
                                            + "values(?,'id_generator',?,?,1,'ID_GENERATOR','',current_timestamp)",
                                    key, wId, wId);// 插入数据库中
                            found = true;
                            break;
                        }
                    }
                }
                if (!found) {
                    wId = list2.size() + 1;
                    t.update(
                            "insert into sys_set(skey,sname,svalue,sort,status,class,remark,time_update) "
                                    + "values(?,'id_generator',?,?,1,'ID_GENERATOR','',current_timestamp)",
                            key, wId, wId);// 插入数据库中
                }
            } else {
                wId = Long.parseLong(list.get(0).get("svalue").toString());
            }
        } catch (Exception e) {
            // Auto-generated catch block
            e.printStackTrace();
        }

        return wId;
    }

    /**
     * @param workerId - 机器ID,范围[0,1024]
     */
    private TwitterSnowflakeIdGenerator(long workerId) {
        Assert.isTrue(workerId <= maxWorkerId && workerId >= 0, "worker Id can't be greater than %d or less than 0");
        this.workerId = workerId;
    }

    public synchronized Long nextId() {
        return getNextId();
    }

    public List<Long> nextIds(int batchSize) {
        synchronized (TwitterSnowflakeIdGenerator.class) {
            Assert.isTrue(batchSize > 1, "worker Id can't be less than 1");
            List<Long> list = new ArrayList<>((int) (batchSize * 1.2));
            for (int i = 0; i < batchSize; i++) {
                list.add(getNextId());
            }
            return list;
        }
    }

    private long getNextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            try {
                throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp)
                        + " milliseconds");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (lastTimestamp == timestamp) {
            // 当前毫秒内,则+1
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 当前毫秒内计数满了,则等待下一秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }
        lastTimestamp = timestamp;
        // ID偏移组合生成最终的ID,并返回ID
        return ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;
    }

    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

}

package com.mn.common.utils;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;

public class NetworkUtil {
    
    private NetworkUtil() {
        
    }
    
    public static boolean internalIp(byte[] addr) {
        final byte b0 = addr[0];
        final byte b1 = addr[1];
        // 10.x.x.x/8
        final byte SECTION_1 = 0x0A;
        // 172.16.x.x/12
        final byte SECTION_2 = (byte) 0xAC;
        final byte SECTION_3 = (byte) 0x10;
        final byte SECTION_4 = (byte) 0x1F;
        // 192.168.x.x/16
        final byte SECTION_5 = (byte) 0xC0;
        final byte SECTION_6 = (byte) 0xA8;
        switch (b0) {
            case SECTION_1:
                return true;
            case SECTION_2:
                if (b1 >= SECTION_3 && b1 <= SECTION_4) {
                    return true;
                }
            case SECTION_5:
                if(b1 == SECTION_6) {
                    return true;
                }
//                switch (b1) {
//                    case SECTION_6:
//                        return true;
//                    default:
//                        break;
//                }
            default:
                return false;
        }
    }

    // 获取第一个内网地址
    public static String getFirstIp() {
        String ipAddress = "";
        try {
            Enumeration<NetworkInterface> interfaces;
            interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface ni = interfaces.nextElement();
                Enumeration<InetAddress> addresss = ni.getInetAddresses();
                while (addresss.hasMoreElements()) {
                    InetAddress nextElement = addresss.nextElement();
                    nextElement.isLoopbackAddress();
                    String hostAddress = nextElement.getHostAddress();
                    if (nextElement instanceof Inet4Address) { // 只关心 IPv4 地址
                        boolean isIn = internalIp(nextElement.getAddress());
                        if (isIn) {// 是内网地址
                            return hostAddress;
                        }
                        System.out.println("网卡接口地址:" + nextElement.getHostAddress());
                        System.out.println("网卡接口地址:" + isIn);
                        // 判断能否到达数据库,如果能到达,选这个
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();

        }
        return ipAddress;
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文