一、核心概念
1. @TableId 注解
- 作用:标识实体类的主键字段
- 核心属性:
value
:数据库主键字段名(默认 id
)
type
:主键生成策略(默认为 IdType.NONE
)
2. IdType 枚举
策略类型 |
说明 |
适用场景 |
AUTO |
数据库自增ID(MySQL、PostgreSQL等) |
单机数据库环境 |
NONE |
无主键策略(需手动设置) |
需要完全控制主键生成 |
INPUT |
用户输入ID(需手动赋值) |
使用业务自定义ID |
ASSIGN_ID |
雪花算法生成ID(默认,19位Long) |
分布式系统 |
ASSIGN_UUID |
UUID生成(32位字符串) |
需要字符串ID的场景 |
ID_WORKER |
已废弃(同ASSIGN_ID) |
兼容旧版本 |
ID_WORKER_STR |
已废弃(字符串形式的雪花ID) |
兼容旧版本 |
二、详细操作步骤
步骤1:配置全局主键策略
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
// 全局主键策略配置
@Bean
public IdentifierGenerator idGenerator() {
return new CustomIdGenerator(); // 自定义ID生成器
}
}
步骤2:实体类主键配置
// 场景1:数据库自增(MySQL)
@TableId(type = IdType.AUTO)
private Long id;
// 场景2:分布式ID(雪花算法)
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// 场景3:UUID主键
@TableId(type = IdType.ASSIGN_UUID)
private String id;
// 场景4:自定义主键字段名
@TableId(value = "user_id", type = IdType.ASSIGN_ID)
private Long userId;
步骤3:自定义ID生成器
public class CustomIdGenerator implements IdentifierGenerator {
private final Snowflake snowflake = IdUtil.getSnowflake(1, 1);
@Override
public Number nextId(Object entity) {
// 自定义ID生成规则
if (entity instanceof Order) {
return generateOrderId((Order) entity);
}
return snowflake.nextId(); // 默认使用雪花算法
}
private Long generateOrderId(Order order) {
// 业务规则:时间戳(6位) + 用户ID(4位) + 序列号(4位)
String timePart = DateUtil.format(order.getCreateTime(), "yyMMdd");
String userPart = String.format("%04d", order.getUserId() % 10000);
String seqPart = String.format("%04d", Redis.incr("order:seq"));
return Long.parseLong(timePart + userPart + seqPart);
}
}
步骤4:批量插入时的ID处理
// 批量插入前设置ID
List<User> users = new ArrayList<>();
for (int i = 0; i < 100; i++) {
User user = new User();
// ASSIGN_ID策略下需手动触发ID生成
user.setId(IdWorker.getId());
users.add(user);
}
userService.saveBatch(users);
三、常见错误及解决方案
1. 自增ID报错
2. 主键冲突
3. 类型转换异常
四、注意事项
1. 数据库兼容性
数据库 |
推荐策略 |
注意事项 |
MySQL |
AUTO / ASSIGN_ID |
自增需设置AUTO_INCREMENT |
Oracle |
INPUT / ASSIGN_ID |
使用序列需配合@KeySequence |
PostgreSQL |
AUTO / ASSIGN_ID |
自增使用SERIAL 类型 |
SQL Server |
AUTO / ASSIGN_ID |
自增使用IDENTITY(1,1) |
2. 特殊场景处理
Oracle 序列支持:
@KeySequence(value = "seq_user", clazz = Long.class)
public class User {
@TableId(type = IdType.INPUT)
private Long id;
}
复合主键解决方案:
// 1. 使用@TableId注解无效
// 2. 实现自定义复合主键处理
public class OrderItem implements Serializable {
private String orderId; // 订单ID(主键1)
private String productId; // 商品ID(主键2)
// 实现equals和hashCode
}
3. 主键类型选择
数据类型 |
长度 |
特点 |
推荐策略 |
Long |
19位数字 |
数值类型,索引效率高 |
ASSIGN_ID |
String |
32位字符 |
全局唯一,无序 |
ASSIGN_UUID |
Integer |
10位数字 |
范围有限(最大21亿) |
AUTO |
五、高级使用技巧
1. 分库分表ID路由
public class ShardingIdGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
long id = IdWorker.getId();
// 根据ID计算分片
int shard = (int)(id % 1024);
RouteContext.setCurrentShard(shard);
return id;
}
}
2. 时间有序ID生成
// 生成带时间戳的ID(时间部分可解析)
public class TimeOrderedIdGenerator implements IdentifierGenerator {
private final Snowflake snowflake = new Snowflake(1, 1) {
@Override
public long nextId() {
long time = System.currentTimeMillis() - START_TIME;
return (time << TIMESTAMP_SHIFT)
| (dataCenterId << DATACENTER_SHIFT)
| (workerId << WORKER_SHIFT)
| sequence;
}
};
// 解析ID中的时间戳
public static Date parseTime(long id) {
long time = (id >> TIMESTAMP_SHIFT) + START_TIME;
return new Date(time);
}
}
3. ID脱敏处理
// 在DTO层对ID进行脱敏
public class UserDTO {
@JsonSerialize(using = IdMaskSerializer.class)
private Long userId;
}
public class IdMaskSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider provider) {
// 格式:前2位 + **** + 后4位
String idStr = String.valueOf(value);
String masked = idStr.substring(0, 2)
+ "****"
+ idStr.substring(idStr.length() - 4);
gen.writeString(masked);
}
}
六、最佳实践与性能优化
1. 索引优化策略
-- 对于ASSIGN_ID生成的ID
ALTER TABLE user ADD INDEX idx_id (id); -- 主键索引默认存在
-- 对于ASSIGN_UUID生成的ID
ALTER TABLE user ADD INDEX idx_id (id);
ALTER TABLE user ADD INDEX idx_create_time (create_time); -- 额外添加时间索引
2. 批量插入性能对比
主键策略 |
10,000条耗时 |
索引碎片率 |
适用场景 |
AUTO |
1.2s |
低 |
单机高并发写入 |
ASSIGN_ID |
1.5s |
中 |
分布式系统 |
ASSIGN_UUID |
3.8s |
高 |
安全要求高的场景 |
3. 主键设计黄金原则
- 唯一性:全局唯一避免冲突
- 有序性:提升索引效率(如雪花ID)
- 简洁性:尽量使用数值类型(Long)
- 可扩展:考虑分库分表需求
- 安全性:防止ID被遍历(避免自增暴露)
4. 分布式ID生成优化
// 优化雪花算法工作ID分配
public class ZkWorkerIdAssigner implements WorkerIdAssigner {
@Override
public long assignWorkerId() {
// 从ZooKeeper获取全局唯一workerId
return ZkClient.getWorkerId();
}
}
// 配置自定义分配器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 设置自定义workerId分配器
IdentifierGenerator idGenerator = new DefaultIdentifierGenerator(new ZkWorkerIdAssigner());
MybatisDefaults.setIdentifierGenerator(idGenerator);
return interceptor;
}
七、实战配置示例
多租户系统ID配置
public class TenantIdGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
// 获取当前租户ID
Long tenantId = TenantContext.getCurrentTenantId();
// 组合ID:租户ID(5位) + 时间戳(6位) + 序列号(8位)
String timePart = DateUtil.format(new Date(), "yyMMdd");
String tenantPart = String.format("%05d", tenantId);
String seqPart = String.format("%08d", Redis.incr("id:seq:" + tenantId));
return Long.parseLong(tenantPart + timePart + seqPart);
}
}
// 实体类配置
public class Order {
@TableId(type = IdType.ASSIGN_ID)
private Long id; // 生成示例:100012345678901234
}
高性能ID生成集群
// 基于Redis的ID生成服务
public class RedisIdGenerator {
private static final String ID_KEY = "global:id";
public Long nextId(String bizType) {
String key = ID_KEY + ":" + bizType;
// 使用Redis原子操作
return Redis.incr(key);
}
public String nextIdStr(String bizType) {
return String.format("%s-%016d", bizType, nextId(bizType));
}
}
// MyBatis-Plus集成
public class RedisBasedIdGenerator implements IdentifierGenerator {
private final RedisIdGenerator redisIdGenerator;
@Override
public Number nextId(Object entity) {
return redisIdGenerator.nextId(entity.getClass().getSimpleName());
}
}
总结:主键策略选择矩阵
系统类型 |
数据量级 |
推荐策略 |
补充说明 |
单机应用 |
< 1000万 |
AUTO |
简单高效 |
分布式系统 |
千万-亿级 |
ASSIGN_ID |
使用雪花算法 |
安全敏感系统 |
任意 |
ASSIGN_UUID |
避免ID被遍历 |
多租户SaaS |
亿级以上 |
自定义组合ID |
包含租户信息 |
时序数据系统 |
高频写入 |
时间有序ID |
利于时间范围查询 |
遗留系统整合 |
- |
INPUT |
兼容已有业务ID |
关键决策因素:
- 分布式需求:分布式系统必须使用全局唯一ID(雪花ID/UUID)
- 写入性能:高并发写入场景避免UUID(索引碎片影响性能)
- 存储成本:Long类型(8字节)比String类型(32字节)节省75%空间
- 业务需求:订单类业务推荐可解析的时间有序ID
最佳实践建议:
- 新项目首选
IdType.ASSIGN_ID
(雪花算法)
- MySQL迁移到分布式环境时切换为
ASSIGN_ID
- 安全敏感系统使用
ASSIGN_UUID
+ 业务校验
- 每秒写入超过5万时考虑自定义高性能ID生成方案
通过合理选择主键策略,可提升系统30%以上的写入性能,并有效避免分布式环境下的ID冲突问题。