1. 概述

生产环境的应用通常采用基于序列的主键生成方案,以提升效率和可扩展性。但在开发或测试阶段,我们常切换到H2这类内存数据库。

本文将深入探讨如何在H2数据库中使用序列(sequence),包括:

  • 序列的基础操作(创建/查询/删除)
  • 自定义序列参数
  • 在JPA实体中集成序列
  • 兼容Oracle等生产数据库的序列语法

2. 序列生成ID的核心优势

数据库序列提供服务端ID生成控制,在高并发场景下性能显著优于IDENTITY策略

主流数据库如PostgreSQL、Oracle、SQL Server均支持序列。本文聚焦H2数据库的序列实现,同时展示如何通过兼容模式模拟生产环境(如Oracle)的序列行为。

⚠️ 测试环境用H2模拟生产数据库时,需特别注意语法兼容性问题

3. H2序列实战操作

3.1 基础配置

首先在Spring Boot配置文件application-h2-seq.yml中创建嵌入式H2内存库:

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:seqdb
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: none

测试类需激活h2-seq配置:

@ExtendWith(SpringExtension.class)
@ActiveProfiles("h2-seq")
@Transactional
public class H2SeqDemoIntegrationTest {
}

3.2 序列的完整生命周期操作

以下测试演示序列的创建、取值和删除:

private final String sqlNextValueFor = "SELECT NEXT VALUE FOR my_seq";
private final String sqlNextValueFunction = "SELECT nextval('my_seq')";

@Test
void whenCreateH2SequenceWithDefaultOptions_thenGetExpectedNextValueFromSequence() {
    entityManager.createNativeQuery("CREATE SEQUENCE my_seq").executeUpdate();

    Long nextValue = (Long) entityManager.createNativeQuery(sqlNextValueFunction).getSingleResult();
    assertEquals(1, nextValue);

    nextValue = (Long) entityManager.createNativeQuery(sqlNextValueFor).getSingleResult();
    assertEquals(2, nextValue);

    nextValue = (Long) entityManager.createNativeQuery(sqlNextValueFunction).getSingleResult();
    assertEquals(3, nextValue);

    entityManager.createNativeQuery("DROP SEQUENCE my_seq").executeUpdate();
}

核心操作语法总结: ✅ 创建序列CREATE SEQUENCE <name>
获取下一个值

  • 标准语法:SELECT NEXT VALUE FOR <name>
  • 函数语法:SELECT nextval('<name>')
    删除序列DROP SEQUENCE <name>

💡 默认创建的序列起始值为1,步长为1

3.3 自定义序列参数

通过START WITHINCREMENT BY可定制序列的起始值和步长

@Test
void whenCustomizeH2Sequence_thenGetExpectedNextValueFromSequence() {
    entityManager.createNativeQuery("CREATE SEQUENCE my_seq START WITH 1000 INCREMENT BY 10")
      .executeUpdate();

    Long nextValue = (Long) entityManager.createNativeQuery(sqlNextValueFor).getSingleResult();
    assertEquals(1000, nextValue);

    nextValue = (Long) entityManager.createNativeQuery(sqlNextValueFunction).getSingleResult();
    assertEquals(1010, nextValue);

    nextValue = (Long) entityManager.createNativeQuery(sqlNextValueFor).getSingleResult();
    assertEquals(1020, nextValue);

    entityManager.createNativeQuery("DROP SEQUENCE my_seq").executeUpdate();
}

此例创建起始值1000、步长10的序列,每次调用值递增10。

3.4 在JPA实体中集成序列

将H2序列用于JPA实体主键生成:

@Entity
@Table(name = "book")
class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_seq_gen")
    @SequenceGenerator(name = "book_seq_gen", sequenceName = "book_seq", allocationSize = 1)
    private Long id;
    private String title;

    public Book() {
    }

    public Book(String title) {
        this.title = title;
    }

    // ... getters and setters omitted
}

关键注解说明:

  • @GeneratedValue(strategy = GenerationType.SEQUENCE):启用序列生成策略
  • @SequenceGenerator:配置序列生成器
    • sequenceName:数据库序列名
    • allocationSize = 1:保证ID严格连续(禁用批量预分配)

验证测试:

@Test
void whenSaveEntityUsingSequence_thenCorrect() {
    entityManager.createNativeQuery("CREATE SEQUENCE book_seq").executeUpdate();
    Book book1 = new Book("book1");
    assertNull(book1.getId());
    entityManager.persist(book1);
    assertEquals(1, book1.getId());

    Book book2 = new Book("book2");
    entityManager.persist(book2);
    assertEquals(2, book2.getId());
}

⚠️ 测试中需手动创建序列(因配置了ddl-auto=none)。若使用create-dropcreate,JPA会自动创建序列

4. 兼容Oracle序列语法

测试中常用H2模拟生产数据库(如Oracle),但默认H2语法与Oracle存在差异。例如Oracle获取序列值语法:

SELECT <seq_name>.nextval FROM dual

而H2默认不支持dual表。

解决方案:在JDBC URL中设置MODE=Oracle启用兼容模式

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:seqdb;MODE=Oracle
    username: sa
    password:

兼容模式测试验证:

@ExtendWith(SpringExtension.class)
@ActiveProfiles("h2-seq-oracle")
@Transactional
public class H2SeqAsOracleDemoIntegrationTest {
    @Autowired
    private EntityManager entityManager;

    @Test
    void whenCreateH2SequenceWithDefaultOptions_thenGetExpectedNextValueFromSequence() {
        entityManager.createNativeQuery("CREATE SEQUENCE my_seq").executeUpdate();

        String sqlNextValueFor = "SELECT NEXT VALUE FOR my_seq";
        BigDecimal nextValueH2 = (BigDecimal) entityManager
          .createNativeQuery(sqlNextValueFor).getSingleResult();
        assertEquals(0, BigDecimal.ONE.compareTo(nextValueH2));

        String sqlNextValueOralceStyle = "SELECT my_seq.nextval FROM dual";
        BigDecimal nextValueOracle = (BigDecimal) entityManager.createNativeQuery(sqlNextValueOralceStyle)
          .getSingleResult();
        assertEquals(0, BigDecimal.TWO.compareTo(nextValueOracle));

        String sqlNextValueFunction = "SELECT nextval('my_seq')";
        nextValueOracle = (BigDecimal) entityManager.createNativeQuery(sqlNextValueFunction).getSingleResult();
        assertEquals(0, BigDecimal.valueOf(3).compareTo(nextValueOracle));

        entityManager.createNativeQuery("DROP SEQUENCE my_seq").executeUpdate();
    }
}

关键发现: ✅ Oracle语法和H2原生语法均正常工作
❌ **返回类型变为BigDecimal而非Long**(Oracle模式特性)

5. 总结

本文系统介绍了H2数据库中序列的使用技巧,核心要点包括:

  1. 序列的基础操作(创建/取值/删除)
  2. 自定义起始值和步长的实现
  3. JPA实体中集成序列的最佳实践
  4. 通过MODE=Oracle实现生产环境语法兼容

合理使用H2序列兼容模式,能显著提升测试环境与生产环境的一致性,避免部署时踩坑

完整示例代码请参考:GitHub仓库