一、核心概念

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报错

  • 错误java.sql.SQLException: Field 'id' doesn't have a default value
  • 原因:数据库表未设置自增属性
  • 解决
    ALTER TABLE user MODIFY id BIGINT AUTO_INCREMENT;
    

2. 主键冲突

  • 错误Duplicate entry '123' for key 'PRIMARY'
  • 场景INPUT策略下重复ID
  • 解决
    // 使用分布式ID生成器保证唯一性
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    

3. 类型转换异常

  • 错误java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
  • 原因:实体类主键类型与数据库类型不匹配
  • 解决
    // 数据库为字符类型,实体类使用String
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    

四、注意事项

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. 主键设计黄金原则

  1. 唯一性:全局唯一避免冲突
  2. 有序性:提升索引效率(如雪花ID)
  3. 简洁性:尽量使用数值类型(Long)
  4. 可扩展:考虑分库分表需求
  5. 安全性:防止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

关键决策因素:

  1. 分布式需求:分布式系统必须使用全局唯一ID(雪花ID/UUID)
  2. 写入性能:高并发写入场景避免UUID(索引碎片影响性能)
  3. 存储成本:Long类型(8字节)比String类型(32字节)节省75%空间
  4. 业务需求:订单类业务推荐可解析的时间有序ID

最佳实践建议

  • 新项目首选 IdType.ASSIGN_ID(雪花算法)
  • MySQL迁移到分布式环境时切换为 ASSIGN_ID
  • 安全敏感系统使用 ASSIGN_UUID + 业务校验
  • 每秒写入超过5万时考虑自定义高性能ID生成方案

通过合理选择主键策略,可提升系统30%以上的写入性能,并有效避免分布式环境下的ID冲突问题。