Support 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 (i.e.,
within the same JVM process) -- for example, integration tests against
embedded databases whose ApplicationContext configuration only differs
with regard to which bean definition profiles are active.

The root cause of such errors is the fact that Spring's
EmbeddedDatabaseFactory (used internally by both the
<jdbc:embedded-database> XML namespace element and the
EmbeddedDatabaseBuilder for Java Config) will set 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. Thus, subsequent attempts to
create an embedded database will not result in a new database. Instead,
the same JDBC connection URL will be reused, and attempts to create a
new embedded database will actually point to an existing embedded
database created from the same configuration.

This commit addresses this common issue by introducing support for
generating unique names for embedded databases. This support can be
enabled via:

 - EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()

 - EmbeddedDatabaseBuilder.generateUniqueName()

 - <jdbc:embedded-database generate-name="true" ... >

Issue: SPR-8849
This commit is contained in:
Sam Brannen
2015-03-21 00:21:59 +01:00
parent 0c9cd4cc32
commit c0fbe0ae5a
9 changed files with 177 additions and 33 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.jdbc.config;
import java.util.function.Predicate;
import javax.sql.DataSource;
import org.junit.Rule;
@@ -74,17 +76,26 @@ public class JdbcNamespaceIntegrationTests {
@Test
public void createWithAnonymousDataSourceAndDefaultDatabaseName() throws Exception {
assertCorrectSetupForSingleDataSource("jdbc-config-db-name-default-and-anonymous-datasource.xml",
DEFAULT_DATABASE_NAME);
(url) -> url.endsWith(DEFAULT_DATABASE_NAME));
}
@Test
public void createWithImplicitDatabaseName() throws Exception {
assertCorrectSetupForSingleDataSource("jdbc-config-db-name-implicit.xml", "dataSource");
assertCorrectSetupForSingleDataSource("jdbc-config-db-name-implicit.xml", (url) -> url.endsWith("dataSource"));
}
@Test
public void createWithExplicitDatabaseName() throws Exception {
assertCorrectSetupForSingleDataSource("jdbc-config-db-name-explicit.xml", "customDbName");
assertCorrectSetupForSingleDataSource("jdbc-config-db-name-explicit.xml", (url) -> url.endsWith("customDbName"));
}
@Test
public void createWithGeneratedDatabaseName() throws Exception {
Predicate<String> urlPredicate = (url) -> url.startsWith("jdbc:hsqldb:mem:");
urlPredicate.and((url) -> !url.endsWith("dataSource"));
urlPredicate.and((url) -> !url.endsWith("shouldBeOverriddenByGeneratedName"));
assertCorrectSetupForSingleDataSource("jdbc-config-db-name-generated.xml", urlPredicate);
}
@Test
@@ -189,14 +200,14 @@ public class JdbcNamespaceIntegrationTests {
}
}
private void assertCorrectSetupForSingleDataSource(String file, String dbName) {
private void assertCorrectSetupForSingleDataSource(String file, Predicate<String> urlPredicate) {
ConfigurableApplicationContext context = context(file);
try {
DataSource dataSource = context.getBean(DataSource.class);
assertNumRowsInTestTable(new JdbcTemplate(dataSource), 1);
assertTrue(dataSource instanceof AbstractDriverBasedDataSource);
AbstractDriverBasedDataSource adbDataSource = (AbstractDriverBasedDataSource) dataSource;
assertThat(adbDataSource.getUrl(), containsString(dbName));
assertTrue(urlPredicate.test(adbDataSource.getUrl()));
}
finally {
context.close();

View File

@@ -17,10 +17,10 @@
package org.springframework.jdbc.datasource.embedded;
import org.junit.Test;
import org.springframework.core.io.ClassRelativeResourceLoader;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.CannotReadScriptException;
import org.springframework.jdbc.datasource.init.ScriptStatementFailedException;
import static org.junit.Assert.*;
import static org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType.*;
@@ -160,14 +160,62 @@ public class EmbeddedDatabaseBuilderTests {
});
}
@Test
public void createSameSchemaTwiceWithoutUniqueDbNames() throws Exception {
EmbeddedDatabase db1 = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(getClass()))//
.addScripts("db-schema-without-dropping.sql").build();
try {
new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(getClass()))//
.addScripts("db-schema-without-dropping.sql").build();
fail("Should have thrown a ScriptStatementFailedException");
}
catch (ScriptStatementFailedException e) {
// expected
}
finally {
db1.shutdown();
}
}
@Test
public void createSameSchemaTwiceWithGeneratedUniqueDbNames() throws Exception {
EmbeddedDatabase db1 = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(getClass()))//
.addScripts("db-schema-without-dropping.sql", "db-test-data.sql")//
.generateUniqueName(true)//
.build();
JdbcTemplate template1 = new JdbcTemplate(db1);
assertNumRowsInTestTable(template1, 1);
template1.update("insert into T_TEST (NAME) values ('Sam')");
assertNumRowsInTestTable(template1, 2);
EmbeddedDatabase db2 = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(getClass()))//
.addScripts("db-schema-without-dropping.sql", "db-test-data.sql")//
.generateUniqueName(true)//
.build();
assertDatabaseCreated(db2);
db1.shutdown();
db2.shutdown();
}
private void doTwice(Runnable test) {
test.run();
test.run();
}
private void assertNumRowsInTestTable(JdbcTemplate template, int count) {
assertEquals(count, template.queryForObject("select count(*) from T_TEST", Integer.class).intValue());
}
private void assertDatabaseCreated(EmbeddedDatabase db) {
assertNumRowsInTestTable(new JdbcTemplate(db), 1);
}
private void assertDatabaseCreatedAndShutdown(EmbeddedDatabase db) {
JdbcTemplate template = new JdbcTemplate(db);
assertEquals("Keith", template.queryForObject("select NAME from T_TEST", String.class));
assertDatabaseCreated(db);
db.shutdown();
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/jdbc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
<embedded-database id="dataSource" database-name="shouldBeOverriddenByGeneratedName" generate-name="true">
<script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
<script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
</embedded-database>
</beans:beans>

View File

@@ -0,0 +1 @@
create table T_TEST (NAME varchar(50) not null);