Embedded Database Support
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
.
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 newDataSource
implementation, such as a connection pool to manage embedded database connections.
We encourage you to contribute extensions to the Infra community at GitHub Issues.