嵌入式数据库支持

infra.jdbc.datasource.embedded 包提供了对嵌入式 Java 数据库引擎的支持。原生支持 HSQL、https://www.h2database.com[H2] 和 Derby。您还可以使用可扩展的 API 插入新的嵌入式数据库类型和 DataSource 实现。

为什么要使用嵌入式数据库?

嵌入式数据库由于其轻量级的性质,在项目的开发阶段非常有用。好处包括易于配置、启动时间快、可测试性以及在开发过程中快速演进 SQL 的能力。

创建嵌入式数据库

您可以将嵌入式数据库实例作为 bean 公开,如下例所示:

include-code::./JdbcEmbeddedDatabaseConfiguration[tag=snippet,indent=0]

@Configuration
public class JdbcEmbeddedDatabaseConfiguration {

  @Bean
  static DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
            .generateUniqueName(true)
            .setType(EmbeddedDatabaseType.H2)
            .addScripts("schema.sql", "test-data.sql")
            .build();
  }

}

前面的配置创建了一个嵌入式 H2 数据库,该数据库使用类路径根目录下的 schema.sqltest-data.sql 资源中的 SQL 进行填充。此外,作为最佳实践,为嵌入式数据库分配了一个唯一生成的名称。嵌入式数据库作为 javax.sql.DataSource 类型的 bean 提供给 Infra 容器,然后可以根据需要将其注入到数据访问对象中。

有关所有支持选项的更多详细信息,请参阅 EmbeddedDatabaseBuilder 的 javadoc

选择嵌入式数据库类型

本节介绍如何选择 Infra 支持的三种嵌入式数据库之一。它包括以下主题:

使用 HSQL

Infra 支持 HSQL 1.8.0 及更高版本。如果未显式指定类型,则 HSQL 是默认的嵌入式数据库。要显式指定 HSQL,请将 embedded-database 标签的 type 属性设置为 HSQL。如果您使用构建器 API,请使用 EmbeddedDatabaseType.HSQL 调用 setType(EmbeddedDatabaseType) 方法。

使用 H2

Infra 支持 H2 数据库。要启用 H2,请将 embedded-database 标签的 type 属性设置为 H2。如果您使用构建器 API,请使用 EmbeddedDatabaseType.H2 调用 setType(EmbeddedDatabaseType) 方法。

使用 Derby

Infra 支持 Apache Derby 10.5 及更高版本。要启用 Derby,请将 embedded-database 标签的 type 属性设置为 DERBY。如果您使用构建器 API,请使用 EmbeddedDatabaseType.DERBY 调用 setType(EmbeddedDatabaseType) 方法。

自定义嵌入式数据库类型

虽然每种支持的类型都带有默认连接设置,但在必要时可以对其进行自定义。以下示例使用带有自定义驱动程序的 H2:

  • Java

@Configuration
public class DataSourceConfig {

  @Bean
  public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setDatabaseConfigurer(EmbeddedDatabaseConfigurers
            .customizeConfigurer(H2, this::customize))
            .addScript("schema.sql")
            .build();
  }

  private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
    return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
      @Override
      public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
        super.configureConnectionProperties(properties, databaseName);
        properties.setDriverClass(CustomDriver.class);
      }
    };
  }
}

使用嵌入式数据库测试数据访问逻辑

嵌入式数据库提供了一种轻量级的方法来测试数据访问代码。下一个示例是使用嵌入式数据库的数据访问集成测试模板。当嵌入式数据库不需要在测试类之间重用时,使用这样的模板对于一次性操作很有用。但是,如果您希望创建一个在测试套件中共享的嵌入式数据库,请考虑使用 Infra TestContext Framework,并将嵌入式数据库配置为 Infra ApplicationContext 中的 bean,如 创建嵌入式数据库 中所述。以下清单显示了测试模板:

  • Java

public class DataAccessIntegrationTestTemplate {

  private EmbeddedDatabase db;

  @BeforeEach
  public void setUp() {
    // 创建一个从默认脚本填充的 HSQL 内存数据库
    // classpath:schema.sql 和 classpath:data.sql
    db = new EmbeddedDatabaseBuilder()
        .generateUniqueName(true)
        .addDefaultScripts()
        .build();
  }

  @Test
  public void testDataAccess() {
    JdbcTemplate template = new JdbcTemplate(db);
    template.query( /* ... */ );
  }

  @AfterEach
  public void tearDown() {
    db.shutdown();
  }

}

为嵌入式数据库生成唯一名称

如果开发团队的测试套件无意中尝试重新创建同一数据库的其他实例,则经常会遇到嵌入式数据库错误。如果 XML 配置文件或 @Configuration 类负责创建嵌入式数据库,然后相应的配置在同一测试套件(即同一 JVM 进程)内的多个测试场景中重复使用,这很容易发生——例如,针对嵌入式数据库的集成测试,其 ApplicationContext 配置仅在哪些 bean 定义配置文件处于活动状态方面有所不同。

此类错误的根本原因是 Infra EmbeddedDatabaseFactory(由 <jdbc:embedded-database> XML 命名空间元素和 Java 配置的 EmbeddedDatabaseBuilder 内部使用)如果未另行指定,会将嵌入式数据库的名称设置为 testdb。对于 <jdbc:embedded-database> 的情况,嵌入式数据库通常被分配一个等于 bean 的 id 的名称(通常类似于 dataSource)。因此,随后创建嵌入式数据库的尝试不会导致新数据库。相反,重用了相同的 JDBC 连接 URL,并且创建新嵌入式数据库的尝试实际上指向了从相同配置创建的现有嵌入式数据库。

为了解决这个常见问题,TODAY Framework 4.0 提供了为嵌入式数据库生成唯一名称的支持。要启用生成名称的使用,请使用以下选项之一。

  • EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()

  • EmbeddedDatabaseBuilder.generateUniqueName()

  • <jdbc:embedded-database generate-name="true" …​ >

扩展嵌入式数据库支持

您可以通过两种方式扩展 Infra JDBC 嵌入式数据库支持:

  • 实现 EmbeddedDatabaseConfigurer 以支持新的嵌入式数据库类型。

  • 实现 DataSourceFactory 以支持新的 DataSource 实现,例如用于管理嵌入式数据库连接的连接池。

我们鼓励您在 GitHub Issues 上向 Infra 社区贡献扩展。