MyBatis-Plus 主键生成策略
本文基于 MyBatis-Plus 最新稳定版本 3.5.16,全面解析主键生成策略的使用方法、适用场景及最佳实践。
一、主键生成策略概览
MyBatis-Plus 提供了灵活且强大的主键生成策略,通过 IdType 枚举类进行配置。合理的选型不仅能保证数据唯一性,还能显著提升系统性能和可扩展性。
1.1 核心枚举值一览
| 策略类型 | 适用场景 | 主键类型 | 数据库要求 | 优缺点分析 |
|---|---|---|---|---|
| AUTO | 单库单表、简单业务 | Long/Integer | 需支持自增 | ✅ 简单高效;❌ 分布式场景受限 |
| ASSIGN_ID | 分布式系统、高并发 | Long/String | 无特殊要求 | ✅ 全局唯一有序;❌ 依赖系统时钟 |
| ASSIGN_UUID | 跨系统唯一标识 | String | 无特殊要求 | ✅ 无需依赖数据库;❌ 无序、占用空间大 |
| INPUT | Oracle序列/业务自定义ID | 任意类型 | 需序列支持 | ✅ 灵活可控;❌ 需手动维护唯一性 |
| NONE | 跟随全局配置 | 任意类型 | 无特殊要求 | ✅ 配置灵活;❌ 未配置时需手动赋值 |
二、默认策略与优先级规则
2.1 默认主键策略
MyBatis-Plus 3.5.16 的默认主键生成策略为 IdType.ASSIGN_ID(雪花算法)。
生效条件:
- 实体类主键字段为
Long或String类型 - 未显式配置
@TableId(type = ...)注解
实现原理:
使用 DefaultIdentifierGenerator 生成 64 位 Snowflake ID:
- 41 位时间戳(毫秒级,可用约 69 年)
- 10 位机器码(支持 1024 台机器)
- 12 位序列号(每毫秒内支持 4096 个 ID)
数据库字段要求:
- BIGINT(对应 Long)
- VARCHAR(19)(对应 String)
@Data
@TableName("user")
public class User {
// 默认使用 ASSIGN_ID(雪花算法),无需显式配置
private Long id;
private String name;
}
2.2 策略优先级
配置优先级遵循以下规则(从高到低):
局部注解(@TableId) > 全局配置 > 默认策略(ASSIGN_ID)
局部覆盖示例:
@TableId(type = IdType.AUTO) // 优先级最高,覆盖全局配置
private Long id;
全局配置示例:
# application.yml
mybatis-plus:
global-config:
db-config:
id-type: assign_uuid # 全局改为UUID策略
三、各策略详解与实战
3.1 AUTO:数据库自增策略
适用场景
- 单库单表的简单业务
- 对主键连续性有要求的场景
- 数据库支持自增功能(MySQL、SQLServer 等)
配置步骤
数据库表设计:
CREATE TABLE user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
age INT
);
实体类配置:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private Integer age;
}
使用示例
// 插入数据时无需设置id,数据库自动生成
User user = new User();
user.setUsername("test_user");
user.setAge(25);
userMapper.insert(user);
// 插入后可直接获取数据库生成的id
Long generatedId = user.getId();
System.out.println("自增主键:" + generatedId);
注意事项
- 数据库必须支持自增(如 MySQL 的
AUTO_INCREMENT) - 主键类型需为
Long或Integer - 分布式场景下多库实例无法保证全局唯一
- 插入前无法获取主键值,需插入后通过查询获取
3.2 ASSIGN_ID:雪花算法(推荐)
适用场景
- 分布式系统、微服务架构
- 分库分表场景
- 需要插入前获取主键值的业务
- 高并发、大数据量场景
核心原理
雪花算法生成的 64 位 Long 型 ID 结构:
0 | 0000000000 0000000000 0000000000 0000000000 0 | 0000000000 | 000000000000
↑ 41位时间戳 ↑ 10位机器码 ↑ 12位序列号
符号位
配置方式
方式一:实体类注解(推荐)
@Data
@TableName("order")
public class Order {
// 显式声明雪花算法(也可省略,因为这是默认策略)
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private Long userId;
private String orderNo;
}
方式二:全局配置
# application.yml
mybatis-plus:
global-config:
db-config:
id-type: assign_id
使用示例
Order order = new Order();
order.setUserId(1001L);
order.setOrderNo("ORD20260210001");
// 插入时无需设置id,MyBatis-Plus自动生成雪花ID
orderMapper.insert(order);
// 插入前主键已生成,可直接获取
System.out.println("雪花算法生成的ID:" + order.getId());
// 输出示例:1765000000000000001
自定义机器码(可选)
在分布式环境中,如果节点较多(超过 1024 个)或需手动指定机器码:
@Configuration
public class MyBatisPlusConfig {
@Bean
public IdentifierGenerator identifierGenerator() {
return new CustomIdentifierGenerator();
}
static class CustomIdentifierGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
// 自定义机器码:数据中心ID + 机器ID
long dataCenterId = 1L; // 数据中心ID(0-31)
long workerId = 1L; // 机器ID(0-31)
// 这里简化实现,实际应使用完整的雪花算法
long timestamp = System.currentTimeMillis();
long sequence = 0L;
return ((timestamp - 1288834974657L) << 22)
| (dataCenterId << 17)
| (workerId << 12)
| sequence;
}
}
}
注意事项
- 主键类型需为
Long或String(存储为字符串形式的 Long 值) - 依赖系统时钟,若时钟回拨可能导致 ID 冲突(生产环境需保证服务器时钟同步)
- 生成的 ID 趋势递增,索引效率高
- 插入前可获取主键值,便于后续业务逻辑处理
3.3 ASSIGN_UUID:UUID 策略
适用场景
- 需要字符串类型主键的场景
- 对有序性无要求的业务
- 跨系统数据交换
- 不依赖数据库特性的场景
配置方式
@Data
@TableName("product")
public class Product {
// 生成 32 位无中划线的 UUID
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String productName;
private BigDecimal price;
}
使用示例
Product product = new Product();
product.setProductName("笔记本电脑");
product.setPrice(new BigDecimal("5999"));
productMapper.insert(product);
// 输出示例:5f8a7b9c1d2e3f4a5b6c7d8e9f0a1b2c
System.out.println("UUID主键:" + product.getId());
注意事项
- 生成 32 位无中划线的 UUID 字符串
- 主键类型必须为
String - UUID 无序,可能影响数据库索引性能(B+ 树索引对有序值更友好)
- 占用存储空间大(32 字节 vs Long 的 8 字节)
- 全局唯一,无需依赖数据库
3.4 INPUT:手动输入策略
适用场景
- Oracle 数据库使用序列生成主键
- 业务自定义主键规则(如订单号、用户编号)
- 从外部系统获取主键值(如分布式 ID 生成器)
- 需要完全自定义主键生成逻辑
配置方式
Oracle 序列方式:
@Data
@TableName("user")
@KeySequence(value = "SEQ_USER", clazz = Long.class)
public class User {
// 使用 INPUT 策略,主键值需手动设置
@TableId(type = IdType.INPUT)
private Long id;
private String name;
}
注册序列生成器:
@Configuration
public class MyBatisPlusConfig {
@Bean
public IKeyGenerator oracleKeyGenerator() {
return new OracleKeyGenerator();
}
}
业务自定义 ID 方式:
@Data
@TableName("custom_code")
public class CustomCode {
@TableId(type = IdType.INPUT)
private String code;
private String name;
}
使用示例
// 方式一:Oracle 序列
User user = new User();
user.setId(sequenceGenerator.nextId("SEQ_USER")); // 调用序列
user.setName("Alice");
userMapper.insert(user);
// 方式二:业务自定义
CustomCode customCode = new CustomCode();
customCode.setCode("CODE20260210001"); // 业务自定义主键
customCode.setName("测试编码");
customCodeMapper.insert(customCode);
注意事项
- 插入前必须手动设置主键值,否则会报错
- 需自行保证主键的唯一性
- Oracle 场景需配合
@KeySequence注解和序列生成器 - 适合对主键规则有特殊要求的业务场景
3.5 NONE:无特定策略
适用场景
- 主键由其他框架或中间件生成
- 需要完全手动控制主键
- 与全局配置配合使用
配置方式
@Data
@TableName("manual_id")
public class ManualId {
@TableId(type = IdType.NONE)
private String customId;
private String name;
}
使用示例
ManualId entity = new ManualId();
entity.setCustomId("CUSTOM_" + System.currentTimeMillis()); // 手动设置
entity.setName("手动主键");
manualIdMapper.insert(entity);
注意事项
- 插入时必须确保主键有值,否则会报错
- 等效于未配置策略,实际使用全局配置
- 插入失败可能导致主键冲突
四、全局配置方式
4.1 YAML 配置(推荐)
# application.yml
mybatis-plus:
global-config:
db-config:
# 全局主键生成策略
id-type: assign_id
# 自定义 ID 生成器
key-generator: com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator
可选值:
auto:数据库自增assign_id:雪花算法(默认)assign_uuid:UUIDinput:手动输入none:无特定策略
4.2 Java 配置类
@Configuration
public class MyBatisPlusConfig {
@Bean
public GlobalConfig globalConfig() {
GlobalConfig config = new GlobalConfig();
// 设置全局主键策略为雪花算法
config.getDbConfig().setIdType(IdType.ASSIGN_ID);
// 自定义 ID 生成器
config.setIdentifierGenerator(new CustomIdentifierGenerator());
return config;
}
// 自定义 ID 生成器实现
static class CustomIdentifierGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
// 自定义 ID 生成逻辑
String prefix = entity.getClass().getSimpleName().toUpperCase();
return Long.parseLong(prefix + System.currentTimeMillis());
}
}
}
4.3 Spring XML 配置(传统 Spring 项目)
<bean id="globalConfig" class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="dbConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
<property name="idType" value="ASSIGN_ID"/>
</bean>
</property>
</bean>
五、主键回填机制
5.1 原理说明
主键回填是指执行 insert() 方法后,MyBatis-Plus 会自动将生成的主键值填充到实体对象的对应属性中,无需额外操作。
5.2 回填验证
User user = new User();
user.setName("Alice");
userMapper.insert(user);
// 主键已自动回填到实体对象中
Long id = user.getId();
System.out.println("回填的主键ID:" + id);
注意:
insert()方法的返回值是影响行数,不是主键值- 主键值通过实体对象获取
- 批量插入时,集合中每个实体的主键均会被回填
5.3 批量插入回填
<!-- UserMapper.xml -->
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name) VALUES
<foreach item="item" collection="list" separator=",">
(#{item.name})
</foreach>
</insert>
注意事项:
- MySQL 需开启
useGeneratedKeys - Oracle 需配合序列使用
IdType.INPUT - PostgreSQL 建议使用
RETURNING子句回填主键
六、自定义主键生成器
6.1 业务 ID 生成器(前缀 + 时间戳)
@Component
public class BizIdGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
// 获取实体类名作为前缀
String prefix = entity.getClass().getSimpleName().toUpperCase();
// 生成业务 ID:前缀 + 时间戳
String bizId = prefix + System.currentTimeMillis();
return Long.parseLong(bizId);
}
}
6.2 分布式 ID 生成器(基于 Redis)
@Component
public class RedisIdGenerator implements IdentifierGenerator {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public Number nextId(Object entity) {
String key = "id:generator:" + entity.getClass().getSimpleName();
// Redis 原子自增
Long id = redisTemplate.opsForValue().increment(key);
// 设置过期时间(可选)
if (id == 1) {
redisTemplate.expire(key, 7, TimeUnit.DAYS);
}
return id;
}
}
6.3 注册自定义生成器
方式一:声明为 Bean
@Component
public class CustomIdGenerator implements IdentifierGenerator {
@Override
public Long nextId(Object entity) {
// 自定义逻辑
return ...;
}
}
方式二:通过配置类注册
@Bean
public IdentifierGenerator customIdGenerator() {
return new CustomIdGenerator();
}
方式三:使用 MybatisPlusPropertiesCustomizer
@Bean
public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
return props ->
props.getGlobalConfig().setIdentifierGenerator(new CustomIdGenerator());
}
七、常见问题与避坑指南
7.1 主键未回填
排查步骤:
- 检查数据库表主键是否设置自增(MySQL 需
AUTO_INCREMENT) - 确认
@TableId注解的type与数据库策略匹配 - 切勿从
insert()返回值获取主键,应从实体对象获取 - 检查是否正确配置了
useGeneratedKeys(批量插入场景)
7.2 时钟回拨问题
现象:
雪花算法依赖系统时钟,若服务器时钟回拨,可能导致 ID 重复。
解决方案:
-
保证服务器时钟同步
- 使用 NTP 服务进行时钟同步
- 生产环境建议配置时钟同步策略
-
使用带检测的雪花算法
- MyBatis-Plus 默认实现已做时钟回拨检测
- 自定义实现时可添加检测逻辑
-
降级策略
- 检测到时钟回拨时,等待或使用备用策略
7.3 UUID 无序导致的性能问题
问题:
UUID 无序特性导致数据插入时频繁发生页分裂,影响索引性能。
解决方案:
-
优先使用 ASSIGN_ID(雪花算法)
- 生成的 ID 有序递增,索引效率高
- 适合分布式场景
-
优化索引设计
- 考虑使用辅助索引
- 对有序性要求高的字段单独设计
-
批量插入优化
- 使用批量插入减少 IO 操作
- 考虑调整索引策略
7.4 类型转换异常
错误示例:
// 错误:ASSIGN_UUID 要求主键类型为 String
@TableId(type = IdType.ASSIGN_UUID)
private Long id; // ❌ 类型不匹配
正确做法:
// 正确:ASSIGN_UUID 主键类型必须为 String
@TableId(type = IdType.ASSIGN_UUID)
private String id; // ✅ 类型正确
7.5 分库分表主键冲突
问题:
在分库分表场景下,使用 AUTO 策略会导致不同分表的主键重复。
解决方案:
-
绝对禁止使用 AUTO
- 分库分表后,不同分表的自增主键会重复
-
优先选择 ASSIGN_ID
- 雪花算法生成的主键全局唯一、有序
- 适合分库分表场景
-
避免使用 ASSIGN_UUID
- 无序主键会导致分表后数据分布不均
八、选型建议与最佳实践
8.1 场景选型对照表
| 业务场景 | 推荐策略 | 理由 |
|---|---|---|
| 小型应用 / 单库单表 | AUTO |
简单高效,依赖数据库原生能力 |
| 分布式系统 / 微服务 | ASSIGN_ID |
全局唯一有序,支持分库分表 |
| 字符串主键 / 无序性要求 | ASSIGN_UUID |
分布式唯一,无需依赖数据库 |
| 业务编码主键 | INPUT |
完全自定义,贴合业务规则 |
| Oracle 数据库 | INPUT + @KeySequence |
使用序列生成主键 |
8.2 最佳实践
-
优先使用默认策略
- 大部分场景下,
ASSIGN_ID(雪花算法)是最佳选择 - 避免过度设计
- 大部分场景下,
-
统一全局配置
- 项目中大部分实体类使用相同策略时,通过全局配置统一设置
- 特殊需求时通过局部注解覆盖
-
类型安全
- 确保实体类主键类型与策略匹配
- 避免类型转换异常
-
时钟同步
- 使用雪花算法时,保证服务器时钟同步
- 避免时钟回拨问题
-
索引优化
- 根据主键策略优化数据库索引
- 有序主键提升查询性能
-
批量操作
- 批量插入时注意主键回填机制
- 配置
useGeneratedKeys参数
8.3 配置优先级总结
九、完整代码示例
9.1 依赖配置
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot 3.x -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.16</version>
</dependency>
<!-- Spring Boot 2.x -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.16</version>
</dependency>
<!-- Spring Boot 4.x -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>3.5.16</version>
</dependency>
</dependencies>
9.2 配置文件
# application.yml
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 驼峰转下划线
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL 日志
global-config:
db-config:
id-type: assign_id # 全局主键策略
table-prefix: tbl_ # 表前缀
9.3 实体类示例
@Data
@TableName("user")
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@TableField("username")
private String username;
private Integer age;
@TableLogic // 逻辑删除字段
private Integer deleted;
@Version // 乐观锁字段
private Integer version;
@TableField(fill = FieldFill.INSERT) // 自动填充
private LocalDateTime createTime;
}
十、总结
MyBatis-Plus 3.5.16 的主键生成策略设计精妙,覆盖了从单库单表到分布式分库分表的全场景需求。掌握主键策略的选型和配置,是开发高效、可扩展系统的核心技能。
核心要点回顾
- 默认策略:
ASSIGN_ID(雪花算法),适合大部分场景 - 配置优先级:局部注解 > 全局配置 > 默认策略
- 主键回填:插入后自动填充到实体对象,通过实体获取主键
- 分布式场景:优先选择
ASSIGN_ID,禁止使用AUTO - 自定义扩展:实现
IdentifierGenerator接口可定制 ID 生成逻辑
升级注意事项
从旧版本升级到 3.5.16 时,注意以下变化:
- 弃用策略:
ID_WORKER、UUID、ID_WORKER_STR已弃用,请使用ASSIGN_ID或ASSIGN_UUID替代 - Spring Boot 4 支持:新增
mybatis-plus-spring-boot4-starter模块 - JSQLParser 模块化:从 3.5.9 开始,JSQLParser 支持已独立,需自行引入
mybatis-plus-jsqlparser或mybatis-plus-jsqlparser-4.9
通过合理选择和配置主键生成策略,MyBatis-Plus 能无缝实现主键管理,显著提升开发效率和系统性能。希望本文能帮助你在实际项目中做出更明智的技术选型!
参考资源:
更多推荐



所有评论(0)