原文网址:MyBatis-Plus--使用雪花算法生成主键ID--使用/分析_IT利刃出鞘的博客-CSDN博客
说明
本文介绍MyBatis-Plus如何使用其自带的雪花算法生成主键ID。
MyBatis-Plus自带的雪花算法
DROP DATABASE IF EXISTS mp;
CREATE DATABASE mp DEFAULT CHARACTER SET utf8;
USE mp;
DROP TABLE IF EXISTS `t_user`;
SET NAMES utf8mb4;
CREATE TABLE `t_user`
(
`id` BIGINT(0) NOT NULL,
`user_name` VARCHAR(64) NOT NULL COMMENT '用户名(不能重复)',
`nick_name` VARCHAR(64) NULL COMMENT '昵称(可以重复)',
`email` VARCHAR(64) COMMENT '邮箱',
`create_time` DATETIME(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`deleted_flag` BIGINT(0) NOT NULL DEFAULT 0 COMMENT '0:未删除 其他:已删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `index_user_name_deleted_flag` (`user_name`, `deleted_flag`),
KEY `index_create_time`(`create_time`)
) ENGINE = InnoDB COMMENT = '用户';
引入MyBatis-Plus依赖。
com.baomidou
mybatis-plus-boot-starter
3.5.1
整个pom.xml为:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.12.RELEASE
com.example
MyBatis-Plus_Single
0.0.1-SNAPSHOT
MyBatis-Plus_Single
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
com.baomidou
mybatis-plus-boot-starter
3.5.1
org.springframework.boot
spring-boot-starter-test
test
mysql
mysql-connector-java
org.projectlombok
lombok
com.github.xiaoymin
knife4j-spring-boot-starter
3.0.3
org.springframework.boot
spring-boot-maven-plugin
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mp?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 222333
#mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
MyBatis-Plus配置类
启动类上加注解:
@MapperScan("com.example.demo.**.mapper")
Entity
package com.example.demo.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName(value = "t_user")
public class User {
@TableId(value = "id")
// 等价于:
// @TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名(不能重复)
*/
private String userName;
/**
* 昵称(可以重复)
*/
private String nickName;
/**
* 邮箱
*/
private String email;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 修改时间
*/
private LocalDateTime updateTime;
/**
* 0:未删除 其他:已删除
*/
@TableLogic(delval = "id")
private Long deletedFlag;
}
Mapper
package com.example.demo.user.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.user.entity.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper extends BaseMapper {
}
Service
接口
package com.example.demo.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.user.entity.User;
public interface UserService extends IService {
}
实现
package com.example.demo.user.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.user.entity.User;
import com.example.demo.user.mapper.UserMapper;
import com.example.demo.user.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl implements UserService {
}
Controller
package com.example.demo.user.controller;
import com.example.demo.user.entity.User;
import com.example.demo.user.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@Api(tags = "增删改查")
@RestController
@RequestMapping("/crud")
public class CRUDController {
@Autowired
private UserService userService;
// 其他代码
@ApiOperation("批量添加")
@PostMapping("/addBatch")
public List addBatch() {
List users = new ArrayList<>();
for (long i = 0; i < 10; i++) {
User user = new User();
user.setUserName("Iron Man" + i);
users.add(user);
}
userService.saveBatch(users);
return users;
}
}
访问:http://localhost:8080/doc.html
结果
访问如下位置:
可以发现,生成的ID是19位的数,而且是增加的。这就是雪花算法的生成的ID的现象。
MyBatis-Plus的ID生成算法的配置方式如下:
application.yml:
mybatis-plus:
global-config:
db-config:
#id类型。
id-type: ASSIGN_ID # 默认为ASSIGN_ID
id-type默认是: ASSIGN_ID,代码如下:
MyBatis的MybatisParameterHandler#populateKeys方法里对ID进行填充,里边调用到IdentifierGenerator#nextId获取ID:
IdentifierGenerator是一个接口,其默认实现类是:DefaultIdentifierGenerator,其调用Sequence的nextId()获取ID:
点进去看看这个Sequence:
可以发现,这就是雪花算法!
结论
MyBatis-Plus生成机器序号的方法是:前5位作为dataCenterId(通过MAC地址生成),后5位作为workerId(通过MAC地址结合JVM的PID生成)。
分析
DefaultIdentifierGenerator有个构造函数,若没有配置workerId和dataCenterId,则会调用无参构造方法。
上边会调用到Sequence的构造方法:Sequence#Sequence(java.net.InetAddress),如下图所示
追踪dataCenterId和workerId的方法:
package com.baomidou.mybatisplus.core.toolkit;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.concurrent.ThreadLocalRandom;
/**
* 分布式高效有序 ID 生产黑科技(sequence)
*
* 优化开源项目:https://gitee.com/yu120/sequence
*
* @author hubin
* @since 2016-08-18
*/
public class Sequence {
// 其他代码
/**
* 获取 maxWorkerId
*/
protected long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (StringUtils.isNotBlank(name)) {
/*
* GET jvmPid
*/
mpid.append(name.split(StringPool.AT)[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* 数据标识id部分
*/
protected long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
if (null == this.inetAddress) {
this.inetAddress = InetAddress.getLocalHost();
}
NetworkInterface network = NetworkInterface.getByInetAddress(this.inetAddress);
if (null == network) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
if (null != mac) {
id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
}
} catch (Exception e) {
logger.warn(" getDatacenterId: " + e.getMessage());
}
return id;
}
}
说明
MyBatis-Plus也提供了一个雪花算法工具类,想用的可以使用:com.baomidou.mybatisplus.core.toolkit.IdWorker。
用法
public static void main(String[] args) {
// 返回值 1385106677482582018
System.out.println(IdWorker.getId());
// 返回值 "1385106677482582019"
System.out.println(IdWorker.getIdStr());
}
注意
这只是个工具类,与MyBatis-Plus和它的ID默认的雪花算法无关。
在github中有一个很流行的分布式统一ID生成框架也叫idworker,需要和mybatis-plus中自带的Idworker工具类区分开来。它是一个基于zookeeper和snowflake算法的分布式统一ID生成工具,通过zookeeper自动注册机器(最多1024台),无需手动指定workerId和dataCenterId。idworker官网:https://github.com/imadcn/idworker