Embedded Database Support

The cn.taketoday.jdbc.datasource.embedded package provides support for embedded Java database engines. Support for HSQL, H2, and Derby is provided natively. You can also use an extensible API to plug in new embedded database types and DataSource implementations.

Why Use an Embedded Database?

An embedded database can be useful during the development phase of a project because of its lightweight nature. Benefits include ease of configuration, quick startup time, testability, and the ability to rapidly evolve your SQL during development.

Creating an Embedded Database

You can expose an embedded database instance as a bean as the following example shows:

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();
  }

}

The preceding configuration creates an embedded H2 database that is populated with SQL from the schema.sql and test-data.sql resources in the root of the classpath. In addition, as a best practice, the embedded database is assigned a uniquely generated name. The embedded database is made available to the Infra container as a bean of type javax.sql.DataSource that can then be injected into data access objects as needed.

See the javadoc for EmbeddedDatabaseBuilder for further details on all supported options.

Selecting the Embedded Database Type

This section covers how to select one of the three embedded databases that Infra supports. It includes the following topics:

Using HSQL

Infra supports HSQL 1.8.0 and above. HSQL is the default embedded database if no type is explicitly specified. To specify HSQL explicitly, set the type attribute of the embedded-database tag to HSQL. If you use the builder API, call the setType(EmbeddedDatabaseType) method with EmbeddedDatabaseType.HSQL.

Using H2

Infra supports the H2 database. To enable H2, set the type attribute of the embedded-database tag to H2. If you use the builder API, call the setType(EmbeddedDatabaseType) method with EmbeddedDatabaseType.H2.

Using Derby

Infra supports Apache Derby 10.5 and above. To enable Derby, set the type attribute of the embedded-database tag to DERBY. If you use the builder API, call the setType(EmbeddedDatabaseType) method with EmbeddedDatabaseType.DERBY.

Customizing the Embedded Database Type

While each supported type comes with default connection settings, it is possible to customize them if necessary. The following example uses H2 with a custom driver:

  • 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);
      }
    };
  }
}

Testing Data Access Logic with an Embedded Database

Embedded databases provide a lightweight way to test data access code. The next example is a data access integration test template that uses an embedded database. Using such a template can be useful for one-offs when the embedded database does not need to be reused across test classes. However, if you wish to create an embedded database that is shared within a test suite, consider using the Infra TestContext Framework and configuring the embedded database as a bean in the Infra ApplicationContext as described in Creating an Embedded Database. The following listing shows the test template:

  • Java

public class DataAccessIntegrationTestTemplate {

  private EmbeddedDatabase db;

  @BeforeEach
  public void setUp() {
    // creates an HSQL in-memory database populated from default scripts
    // classpath:schema.sql and 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();
  }

}

Generating Unique Names for Embedded Databases

Development teams often encounter errors with embedded databases if their test suite inadvertently attempts to recreate additional instances of the same database. This can happen quite easily if an XML configuration file or @Configuration class is responsible for creating an embedded database and the corresponding configuration is then reused across multiple testing scenarios within the same test suite (that is, within the same JVM process) — for example, integration tests against embedded databases whose ApplicationContext configuration differs only with regard to which bean definition profiles are active.

The root cause of such errors is the fact that Infra EmbeddedDatabaseFactory (used internally by both the <jdbc:embedded-database> XML namespace element and the EmbeddedDatabaseBuilder for Java configuration) sets the name of the embedded database to testdb if not otherwise specified. For the case of <jdbc:embedded-database>, the embedded database is typically assigned a name equal to the bean’s id (often, something like dataSource). Thus, subsequent attempts to create an embedded database do not result in a new database. Instead, the same JDBC connection URL is reused, and attempts to create a new embedded database actually point to an existing embedded database created from the same configuration.

To address this common issue, TODAY Framework 4.2 provides support for generating unique names for embedded databases. To enable the use of generated names, use one of the following options.

  • EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()

  • EmbeddedDatabaseBuilder.generateUniqueName()

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

Extending the Embedded Database Support

You can extend Infra JDBC embedded database support in two ways:

  • Implement EmbeddedDatabaseConfigurer to support a new embedded database type.

  • Implement DataSourceFactory to support a new DataSource implementation, such as a connection pool to manage embedded database connections.

We encourage you to contribute extensions to the Infra community at GitHub Issues.