无法直接通过自定义@sequence注解实现jdbc并发安全的全局唯一序列,因其仅为元数据;需结合数据库表+原子update操作(如update...returning)、jdbc手动管理及aop/mybatis拦截器自动注入才能达成目标。

Java 中无法直接通过自定义 @Sequence 注解实现“JDBC 并发安全的全局唯一序列”,因为注解本身只是元数据,不包含执行逻辑;真正起作用的是配套的序列生成机制。要达成目标,核心在于:**用数据库表 + 原子更新(如 UPDATE ... RETURNING 或 SELECT FOR UPDATE)配合 JDBC 手动管理序列值,再通过自定义注解触发该逻辑**。
1. 自定义 @Sequence 注解声明序列策略
定义一个轻量级注解,用于标记需要序列值的字段或方法,并携带序列名、步长等元信息:
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sequence {
String value() default "default_seq"; // 序列名
int step() default 1; // 每次获取的步长(支持批量预取)
boolean cache() default true; // 是否启用本地缓存(减少 DB 查询)
}这个注解不干活,只做标识——后续需配合 AOP 或 ORM 插件(如 MyBatis 拦截器、Hibernate IdentifierGenerator)解析并执行真正的序列获取逻辑。
2. 基于数据库表的并发安全序列生成器
在 MySQL 中建一张序列表(支持高并发):
CREATE TABLE `sequence_generator` (
`name` VARCHAR(64) PRIMARY KEY,
`next_val` BIGINT NOT NULL DEFAULT 1,
`step` INT NOT NULL DEFAULT 1
);
INSERT INTO sequence_generator(name, next_val, step) VALUES ('order_id', 1, 10);关键:使用 UPDATE ... SET next_val = next_val + ? WHERE name = ? 配合 SELECT LAST_INSERT_ID() 或(MySQL 8.0+)UPDATE ... RETURNING next_val - ? 获取旧值。这是原子操作,天然并发安全。
示例 JDBC 代码(获取下一个序列值):
String sql = "UPDATE sequence_generator SET next_val = next_val + ? WHERE name = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, step);
ps.setString(2, seqName);
if (ps.executeUpdate() == 0) {
throw new IllegalStateException("Sequence not found: " + seqName);
}
}
// 再查一次当前值(减去 step 得到本次分配起点)
sql = "SELECT next_val - ? FROM sequence_generator WHERE name = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, step);
ps.setString(2, seqName);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getLong(1);
}
}3. 结合 AOP 或 MyBatis 拦截器自动注入序列值
以 MyBatis 为例,在插入前拦截 @Insert 方法,扫描参数对象中的 @Sequence 字段,调用上述序列生成器填充值:
- 编写
Interceptor,监听Executor.update - 反射检查入参对象,找到标注
@Sequence的字段 - 若字段为空,调用
SequenceGenerator.next(seqName, step)赋值 - 确保该过程在同一个事务内完成(避免序列分配后插入失败导致空洞)
这样开发者只需写:
public class Order {
@Sequence("order_id")
private Long id;
private String title;
}4. 注意事项与优化点
-
避免单点瓶颈:一张表所有序列共用会成为热点,可按业务拆分多张表或加分片键(如
name_hash % N) - 步长不是越大越好:大步长提升吞吐但增加空洞和内存占用;建议 10~100,结合缓存使用
-
MySQL 版本差异:5.7 不支持
RETURNING,需用两阶段更新+查询;8.0+ 可用UPDATE ... RETURNING减少一次 round-trip - 不依赖 AUTO_INCREMENT:完全绕过 MySQL 自增机制,适用于分库分表、多主部署等场景
自定义注解是入口,底层靠数据库行锁保障并发安全,JDBC 是执行载体——三者结合才能真正落地。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











