Mybatis-Plus雪花id的使⽤以及解析机器ID和数据标识ID实现
概述
分布式系统中,有⼀些需要使⽤全局唯⼀ID的场景,这种时候为了防⽌ID冲突可以使⽤36位的UUID,但是UUID有⼀些缺点,⾸先他相对⽐较长,另外UUID⼀般是⽆序的。rotten
有些时候我们希望能使⽤⼀种简单⼀些的ID,并且希望ID能够按照时间有序⽣成。
⽽twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID⽣成机制,所以开发了这样⼀套全局唯⼀ID⽣成服务。
结构
snowflake的结构如下(每部分⽤-分开):
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
第⼀位为未使⽤,接下来的41位为毫秒级时间(41位的长度可以使⽤69年),然后是5位datacenterId和5位workerId(10位的长度最多⽀持部署1024个节点),最后12位是毫秒内的计数(12位的计数顺序号⽀持每个节点每毫秒产⽣4096个ID序号)
⼀共加起来刚好64位,为⼀个Long型。(转换成字符串后长度最多19)
snowflake⽣成的ID整体上按照时间⾃增排序,并且整个分布式系统内不会产⽣ID碰撞(由datacenter和workerId作区分),并且效率较⾼。经测试snowflake每秒能够产⽣26万个ID。
源码
/**
* Twitter_Snowflake<br>
* SnowFlake的结构如下(每部分⽤-分开):<br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* 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左右。
*/
public class SnowflakeIdWorker {
// ==============================Fields===========================================
/** 开始时间截 (2015-01-01) */
private final long twepoch = 1420041600000L;
/** 机器id所占的位数 */
private final long workerIdBits = 5L;
/** 数据标识id所占的位数 */
private final long datacenterIdBits = 5L;
/** ⽀持的最⼤机器id,结果是31 (这个移位算法可以很快的计算出⼏位⼆进制数所能表⽰的最⼤⼗进制数) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** ⽀持的最⼤数据标识id,结果是31 */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** 序列在id中占的位数 */
private final long quenceBits = 12L;
/** 机器ID向左移12位 */
private final long workerIdShift = quenceBits;
/** 数据标识id向左移17位(12+5) */
private final long datacenterIdShift = quenceBits + workerIdBits;
/** 时间截向左移22位(5+5+12) */
private final long timestampLeftShift = quenceBits + workerIdBits + datacenterIdBits;
/** ⽣成序列的掩码,这⾥为4095 (0b111111111111=0xfff=4095) */
private final long quenceMask = -1L ^ (-1L << quenceBits);
/** ⼯作机器ID(0~31) */
private long workerId;
/** 数据中⼼ID(0~31) */
private long datacenterId;
/** 毫秒内序列(0~4095) */
private long quence = 0L;
/** 上次⽣成ID的时间截 */
private long lastTimestamp = -1L;
//==============================Constructors=====================================
/**
* 构造函数2012年高考数学试题
* @param workerId ⼯作ID (0~31)
* @param datacenterId 数据中⼼ID (0~31)
*/
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;
}
// ==============================Methods==========================================
lending club
/**
* 获得下⼀个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 milliconds", lastTimestamp - timestamp));
}
//如果是同⼀时间⽣成的,则进⾏毫秒内序列
roll the dice
if (lastTimestamp == timestamp) {
quence = (quence + 1) & quenceMask;
//毫秒内序列溢出
if (quence == 0) {
//阻塞到下⼀个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//时间戳改变,毫秒内序列重置
el {
quence = 0L;
}
软件设计培训
//上次⽣成ID的时间截
lastTimestamp = timestamp;
fenzheng//移位并通过或运算拼到⼀起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //名人英语演讲
| quence;
}
/**
* 阻塞到下⼀个毫秒,直到获得新的时间戳
* @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(0, 0);
for (int i = 0; i < 1000; i++) {
卤碱long id = Id();
System.out.BinaryString(id));
System.out.println(id);
}
}
}
Mybatis-Plus使⽤雪花id
注意:以下配置是在SpringBoot2.1.4版本以及在mybatis已经可以使⽤的基础上做的升级配置!
1.引⼊Mybatis-Plus依赖(3.1.1版本⽬前有些问题,建议使⽤3.1.0版本)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
2.在l配置⽂件中增加如下配置项
mybatis-plus:
#mapper-locations: classpath:mybatis/**/*l
# 在classpath前添加星号可以使项⽬热加载成功
mapper-locations: classpath*:mybatis/**/*l
#实体扫描,多个package⽤逗号或者分号分隔
typeAliasPackage: com.nis.project
global-config:
#主键类型 0:"数据库ID⾃增", 1:"⽤户输⼊ID",2:"全局唯⼀ID (数字类型唯⼀ID)", 3:"全局唯⼀ID UUID";
id-type: 3
#机器 ID 部分(影响雪花ID)
workerId: 1
#数据标识 ID 部分(影响雪花ID)(workerId 和 datacenterId ⼀起配置才能重新初始化 Sequence)
datacenterId: 18
#字段策略 0:"忽略判断",1:"⾮ NULL 判断"),2:"⾮空判断"
field-strategy: 2
#驼峰下划线转换
db-column-underline: true
#刷新mapper 调试神器
refresh-mapper: true
#数据库⼤写下划线转换
#capital-mode: true
#序列接⼝实现类配置
#key-generator: com.
#逻辑删除配置(下⾯3个配置)
logic-delete-value: 0
logic-not-delete-value: 1
#⾃定义SQL注⼊器
#sql-injector: batisplus.mapper.LogicSqlInjector
#⾃定义填充策略接⼝实现
#meta-object-handler: com.
configuration:
map-underscore-to-camel-ca: true
cache-enabled: fal
# 这个配置会将执⾏的sql打印出来,在开发或测试的时候可以⽤
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.原有的mapper接⼝增加继承BaMapper接⼝
服诺留学
public interface UrMapper extends BaMapper<Ur>
4.实体类增加注解
在Ur实体类上添加@TableId的注解⽤来标识实体类的主键,以便插件在⽣成主键雪花Id的时候找到哪个是主键。
@TableId
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long urId;
5.分页配置
5.1 添加mybatis的⼀个配置类(不添加,则分页数据中的page参数会异常)
package com.fig;
import sion.plugins.PaginationInterceptor;
batis.spring.annotation.MapperScan;
import t.annotation.Bean;
import t.annotation.Configuration;
import ansaction.annotation.EnableTransactionManagement;
/**
* Mybatis-Plus插件配置类
*
* @author lwj
*/
@EnableTransactionManagement
@Configuration
@MapperScan({"com.nis.project.*.mapper","com.nis.project.*.*.mapper"})
public class MybatisPlusConfig {
/
**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
5.2 java代码⽰例
Controller类中添加page分页对象。
@GetMapping("/list")
@ResponBody
public Msg list(Ur ur, HttpServletRequest request) {
Map<String, Object> params = vertDataMap(request);
Page<Ur> page = startMybatisPlusPage(ur);
IPage<Ur> urIPage = urService.lectUrList(page, ur);
return Msg.success(urIPage);
}
ServiceImpl中的具体⽅法
soga什么意思@Override
@DataScope(tableAlias = "b")
public IPage<Ur> lectUrList(Page<Ur> page, Ur ur) {
return UrMapper.lectUrList(page, ur);
}
mapper接⼝中的代码
IPage<Ur> lectUrList(@Param("pg") Page<Ur> page, @Param("ps") Ur ur);
mybaits的xml⽂件中写的sql代码
<lect id="lectUrList" resultType="com.nis.project.system.ur.domain.Ur" parameterType="com.nis.project.system.ur.domain.Ur"> lect
a.ur_id,
a.dept_id,
a.login_name,
a.ur_name,
a.phone_number,
a.x,
a.avatar,
a.status,
a.login_ip,
a.login_date,
b.dept_name
from sys_ur a
left join sys_dept b on b.dept_id = a.dept_id
where 1=1
<if test="ps.loginName != null and ps.loginName != ''">and a.login_name like concat('%', #{ps.loginName}, '%')</if>
<if test="ps.urName != null and ps.urName != ''">and a.ur_name like concat('%', #{ps.urName}, '%')</if>
<if test="ps.deptId != null and ps.deptId != ''">and b.dept_id = #{ps.deptId}</if>
<if test="ps.email != null ail != ''">ail = #{ps.email}</if>
<if test="ps.phoneNumber != null and ps.phoneNumber != ''">and a.phone_number like concat('%', #{ps.phoneNumber}, '%')</if>
<if test="ps.params.deptIds != null and ps.params.deptIds != ''">and find_in_t(a.dept_id,#{ps.params.deptIds})</if>
<if test="ps.status != null and ps.status != ''">and a.status = #{ps.status}</if>
<!-- 数据范围过滤 -->
${ps.params.dataScope}
order by b.der_num
</lect>
⽣成雪花ID以及解析雪花ID的机器ID和数据中⼼ID
1.⽣成雪花ID
这⾥不再阐述,直接贴上⽣成的⼀个雪花ID;1146667501642584065
2.解析雪花ID的机器ID和数据中⼼ID
SELECT (1146667501642584065>>12)&0x1f as workerId,(1146667501642584065>>17)&0x1f as datacenterId;
结果图:
多⽀持!