Mybatis Common Mapper主键生成策略:GenId接口自定义实现

Mybatis Common Mapper主键生成策略:GenId接口自定义实现

【免费下载链接】Mapper Mybatis Common Mapper - Easy to use 【免费下载链接】Mapper 项目地址: 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实现类的生命周期管理,其核心执行流程如下:

mermaid

缓存机制采用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生成器,支持号段模式和雪花模式,集成步骤如下:

  1. 添加Maven依赖
<dependency>
    <groupId>com.robert.vesta</groupId>
    <artifactId>vesta-service</artifactId>
    <version>0.0.1</version>
</dependency>
  1. 实现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();
    }
}
  1. 实体类配置
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 性能对比

生成策略单线程TPS10线程并发TPSID有序性分布式支持实现复杂度
数据库自增5000-80003000-5000有序
UUID15000-200008000-12000无序
雪花算法20000-3000015000-25000有序
Vesta号段模式50000-8000040000-60000有序

6.2 避坑指南

  1. 线程安全问题:确保genId方法是线程安全的,推荐使用synchronized或原子类
  2. 时钟回拨处理:分布式环境需处理服务器时钟不一致问题
  3. 实例管理:避免在GenId实现类中持有大量资源,利用缓存机制减少实例创建
  4. 异常处理:妥善处理ID生成失败场景,避免影响主业务流程
  5. 类型匹配:确保生成的ID类型与数据库字段类型匹配(如Long对应BIGINT)

6.3 扩展建议

  1. 动态数据源适配:根据table参数动态选择不同的ID生成策略
  2. ID生成监控:添加metrics监控ID生成性能和成功率
  3. 预热与降级:实现ID生成器的预热加载和降级策略
  4. 配置化支持:通过配置中心动态调整生成器参数

7. 总结与展望

GenId接口为Mybatis Common Mapper提供了灵活的主键生成扩展点,通过本文介绍的三种实现方案,你可以根据项目需求选择合适的ID生成策略。从简单的UUID到复杂的分布式发号器,GenId接口都能无缝集成,既解决了传统自增主键的扩展性问题,又保证了分布式环境下的ID唯一性。

随着微服务架构的普及,主键生成作为基础设施的重要组成部分,其稳定性和性能将直接影响整个系统的可用性。Mybatis Common Mapper的GenId接口通过"约定优于配置"的设计理念,简化了集成流程,让开发者可以专注于业务逻辑实现。未来,我们可以期待更多创新的ID生成算法与GenId接口结合,为分布式系统提供更高效、更可靠的主键解决方案。

【免费下载链接】Mapper Mybatis Common Mapper - Easy to use 【免费下载链接】Mapper 项目地址: https://gitcode.com/gh_mirrors/ma/Mapper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值