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; } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。