Mybatis Common Mapper主键生成策略:GenId接口自定义实现
【免费下载链接】Mapper Mybatis Common Mapper - Easy to use 项目地址: https://gitcode.com/gh_mirrors/ma/Mapper
1. 痛点解析:传统主键生成方案的局限性
你是否还在为分布式系统中的ID冲突问题焦头烂额?是否受限于数据库自增主键的扩展性瓶颈?Mybatis Common Mapper提供的GenId接口(生成器接口)彻底解决了这些痛点,通过Java代码层面实现灵活可控的主键生成逻辑,完美适配分布式、高并发场景。本文将深入剖析GenId接口的实现原理,带你掌握3种实用自定义实现方案,并通过完整案例演示从编码到集成的全流程。
读完本文你将获得:
- 理解GenId接口的设计原理与执行流程
- 掌握UUID、雪花算法、分布式ID生成器的集成方案
- 学会在Spring环境中优雅使用GenId接口
- 规避主键生成过程中的线程安全与性能陷阱
2. GenId接口架构与工作原理
2.1 接口定义与核心方法
GenId接口是Mybatis Common Mapper提供的主键生成器SPI(服务提供者接口),位于tk.mybatis.mapper.genid包中,定义如下:
public interface GenId<T> {
T genId(String table, String column);
class NULL implements GenId {
@Override
public Object genId(String table, String column) {
throw new UnsupportedOperationException();
}
}
}
关键参数说明:
table:当前操作的数据库表名column:主键对应的数据库列名- 返回值
T:生成的主键值,支持泛型(Long/String等)
2.2 执行流程与缓存机制
GenIdUtil工具类负责GenId实现类的生命周期管理,其核心执行流程如下:
缓存机制采用ConcurrentHashMap实现,配合ReentrantLock双重检查锁定(Double-Checked Locking)确保线程安全,避免频繁创建实例带来的性能损耗:
// GenIdUtil核心代码片段
if (CACHE.containsKey(genClass)) {
genId = CACHE.get(genClass);
} else {
LOCK.lock();
try {
if (!CACHE.containsKey(genClass)) {
CACHE.put(genClass, genClass.newInstance());
}
genId = CACHE.get(genClass);
} finally {
LOCK.unlock();
}
}
3. 注解配置与集成方式
3.1 @KeySql注解参数详解
通过@KeySql注解的genId属性指定自定义实现类,该注解位于tk.mybatis.mapper.annotation包:
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface KeySql {
// 其他属性省略...
Class<? extends GenId> genId() default GenId.NULL.class;
}
属性优先级:useGeneratedKeys > dialect > sql > genId,当指定genId时,将优先使用Java代码生成主键。
3.2 实体类配置示例
在实体类的主键字段上添加注解,指定GenId实现类:
public class Country implements Serializable {
@Id
@KeySql(genId = SimpleGenId.class) // 关联自定义生成器
private Long id;
private String countryname;
private String countrycode;
// Getter/Setter省略
}
4. 三种实用GenId实现方案
4.1 UUID生成器(字符串主键)
UUID(通用唯一识别码)生成器适合需要全局唯一性且不依赖数据库的场景,实现简单无需额外依赖:
public class UUIdGenId implements GenId<String> {
@Override
public String genId(String table, String column) {
// 移除UUID中的横线,缩短长度
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
特点分析:
- 优点:实现简单、全局唯一、无网络依赖
- 缺点:字符串存储占用空间大、无序性影响索引性能
- 适用场景:中小规模系统、对ID有序性无要求的场景
4.2 雪花算法实现(分布式Long型主键)
雪花算法(Snowflake)生成64位Long型ID,包含时间戳、机器ID和序列号,适合分布式系统:
public class SnowflakeGenId implements GenId<Long> {
private final long workerId; // 机器ID(0-31)
private final long datacenterId; // 数据中心ID(0-31)
private long sequence = 0L; // 序列号(0-4095)
private long lastTimestamp = -1L; // 上次生成ID的时间戳
// 构造函数初始化机器ID和数据中心ID
public SnowflakeGenId(long workerId, long datacenterId) {
if (workerId > 31 || workerId < 0) {
throw new IllegalArgumentException("Worker ID必须在0-31之间");
}
if (datacenterId > 31 || datacenterId < 0) {
throw new IllegalArgumentException("数据中心ID必须在0-31之间");
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
@Override
public synchronized Long genId(String table, String column) {
long timestamp = System.currentTimeMillis();
// 时钟回拨处理
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨异常,拒绝生成ID");
}
// 同一毫秒内序列号自增
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & 4095; // 4095 = 2^12 - 1
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp); // 等待下一毫秒
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// 组合64位ID: 0(符号位) + 41位时间戳 + 5位数据中心ID + 5位机器ID + 12位序列号
return ((timestamp - 1609459200000L) << 22)
| (datacenterId << 17)
| (workerId << 12)
| sequence;
}
// 等待到下一毫秒
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
ID结构解析:
0 11111111111111111111111111111111111111111111111111111 11111 11111 111111111111
┬ └─────────────────────────────────────────────────────┬ ────┬ ────┬ ────────────┬
│ 41位时间戳 │5位数据│5位机器│ 12位序列号 │
│ │中心ID │ ID │ │
使用方式:在Spring配置类中定义单例Bean:
@Configuration
public class IdGeneratorConfig {
@Bean
public GenId<Long> snowflakeGenId() {
// 实际环境中机器ID应从配置中心获取
return new SnowflakeGenId(1, 1);
}
}
4.3 分布式发号器集成(以Vesta为例)
Vesta是开源的分布式ID生成器,支持号段模式和雪花模式,集成步骤如下:
- 添加Maven依赖:
<dependency>
<groupId>com.robert.vesta</groupId>
<artifactId>vesta-service</artifactId>
<version>0.0.1</version>
</dependency>
- 实现GenId接口:
public class VestaGenId implements GenId<Long> {
// 通过Spring上下文获取Vesta的IdService
private IdService idService = SpringContextHolder.getBean(IdService.class);
@Override
public Long genId(String table, String column) {
// 调用Vesta服务生成ID
return idService.genId();
}
}
- 实体类配置:
public class Order {
@Id
@KeySql(genId = VestaGenId.class)
private Long id;
private String orderNo;
private BigDecimal amount;
// Getter/Setter省略
}
5. 完整集成案例:从实现到测试
5.1 项目结构与配置
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ ├── entity/
│ │ │ └── User.java // 实体类
│ │ ├── mapper/
│ │ │ └── UserMapper.java // Mapper接口
│ │ └── generator/
│ │ ├── UUIdGenId.java // UUID生成器
│ │ └── SnowflakeGenId.java // 雪花算法生成器
│ └── resources/
│ └── mybatis-config.xml // Mybatis配置
└── test/
└── java/
└── com/
└── mapper/
└── UserMapperTest.java // 测试类
5.2 实体类完整代码
package com.entity;
import jakarta.persistence.Id;
import tk.mybatis.mapper.annotation.KeySql;
import com.generator.SnowflakeGenId;
public class User {
@Id
@KeySql(genId = SnowflakeGenId.class) // 使用雪花算法生成主键
private Long id;
private String username;
private String email;
private LocalDateTime createTime;
// Getter和Setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
}
5.3 Mapper接口与测试类
public interface UserMapper extends Mapper<User> {
// 继承BaseMapper的CRUD方法
}
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testInsert() {
User user = new User();
user.setUsername("test_genid");
user.setEmail("genid@example.com");
user.setCreateTime(LocalDateTime.now());
// 插入时会自动生成ID
int rows = userMapper.insert(user);
Assert.assertEquals(1, rows);
Assert.assertNotNull(user.getId()); // 验证ID已生成
System.out.println("生成的主键ID: " + user.getId());
}
@Test
public void testBatchInsert() {
// 测试并发环境下的ID唯一性
ExecutorService executor = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(1000);
Set<Long> idSet = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
try {
User user = new User();
user.setUsername(Thread.currentThread().getName());
user.setEmail(UUID.randomUUID().toString() + "@test.com");
user.setCreateTime(LocalDateTime.now());
userMapper.insert(user);
idSet.add(user.getId());
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
// 验证1000个ID全部唯一
Assert.assertEquals(1000, idSet.size());
}
}
6. 性能优化与最佳实践
6.1 性能对比
| 生成策略 | 单线程TPS | 10线程并发TPS | ID有序性 | 分布式支持 | 实现复杂度 |
|---|---|---|---|---|---|
| 数据库自增 | 5000-8000 | 3000-5000 | 有序 | 差 | 低 |
| UUID | 15000-20000 | 8000-12000 | 无序 | 优 | 低 |
| 雪花算法 | 20000-30000 | 15000-25000 | 有序 | 优 | 中 |
| Vesta号段模式 | 50000-80000 | 40000-60000 | 有序 | 优 | 高 |
6.2 避坑指南
- 线程安全问题:确保
genId方法是线程安全的,推荐使用synchronized或原子类 - 时钟回拨处理:分布式环境需处理服务器时钟不一致问题
- 实例管理:避免在GenId实现类中持有大量资源,利用缓存机制减少实例创建
- 异常处理:妥善处理ID生成失败场景,避免影响主业务流程
- 类型匹配:确保生成的ID类型与数据库字段类型匹配(如Long对应BIGINT)
6.3 扩展建议
- 动态数据源适配:根据
table参数动态选择不同的ID生成策略 - ID生成监控:添加metrics监控ID生成性能和成功率
- 预热与降级:实现ID生成器的预热加载和降级策略
- 配置化支持:通过配置中心动态调整生成器参数
7. 总结与展望
GenId接口为Mybatis Common Mapper提供了灵活的主键生成扩展点,通过本文介绍的三种实现方案,你可以根据项目需求选择合适的ID生成策略。从简单的UUID到复杂的分布式发号器,GenId接口都能无缝集成,既解决了传统自增主键的扩展性问题,又保证了分布式环境下的ID唯一性。
随着微服务架构的普及,主键生成作为基础设施的重要组成部分,其稳定性和性能将直接影响整个系统的可用性。Mybatis Common Mapper的GenId接口通过"约定优于配置"的设计理念,简化了集成流程,让开发者可以专注于业务逻辑实现。未来,我们可以期待更多创新的ID生成算法与GenId接口结合,为分布式系统提供更高效、更可靠的主键解决方案。
【免费下载链接】Mapper Mybatis Common Mapper - Easy to use 项目地址: https://gitcode.com/gh_mirrors/ma/Mapper
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



