diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 0d8dd371b8c..f7834620e37 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.jdbc; import java.nio.charset.Charset; import java.util.LinkedHashMap; import java.util.Map; +import java.util.UUID; import javax.sql.DataSource; @@ -57,6 +58,11 @@ public class DataSourceProperties */ private String name = "testdb"; + /** + * Generate a random datasource name. + */ + private boolean generateUniqueName; + /** * Fully qualified name of the connection pool implementation to use. By default, it * is auto-detected from the classpath. @@ -148,6 +154,8 @@ public class DataSourceProperties private Xa xa = new Xa(); + private String uniqueName; + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; @@ -183,6 +191,14 @@ public class DataSourceProperties this.name = name; } + public boolean isGenerateUniqueName() { + return this.generateUniqueName; + } + + public void setGenerateUniqueName(boolean generateUniqueName) { + this.generateUniqueName = generateUniqueName; + } + public Class getType() { return this.type; } @@ -268,7 +284,7 @@ public class DataSourceProperties if (StringUtils.hasText(this.url)) { return this.url; } - String url = this.embeddedDatabaseConnection.getUrl(this.name); + String url = this.embeddedDatabaseConnection.getUrl(determineDatabaseName()); if (!StringUtils.hasText(url)) { throw new DataSourceBeanCreationException(this.embeddedDatabaseConnection, this.environment, "url"); @@ -276,6 +292,16 @@ public class DataSourceProperties return url; } + private String determineDatabaseName() { + if (this.generateUniqueName) { + if (this.uniqueName == null) { + this.uniqueName = UUID.randomUUID().toString(); + } + return this.uniqueName; + } + return this.name; + } + /** * Return the configured username or {@code null} if none was configured. * @return the configured username diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDataSourceConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDataSourceConfiguration.java index 60bd9f2c6ce..d61c9c45aa0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDataSourceConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDataSourceConfiguration.java @@ -55,7 +55,8 @@ public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware { public EmbeddedDatabase dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseConnection.get(this.classLoader).getType()); - this.database = builder.setName(this.properties.getName()).build(); + this.database = builder.setName(this.properties.getName()) + .generateUniqueName(this.properties.isGenerateUniqueName()).build(); return this.database; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java index 294c13ac628..b5c7eaaafb3 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java @@ -66,6 +66,19 @@ public class DataSourcePropertiesTests { assertThat(properties.determineUrl()).isEqualTo("jdbc:mysql://mydb"); } + @Test + public void determineUrlWithGenerateUniqueName() throws Exception { + DataSourceProperties properties = new DataSourceProperties(); + properties.setGenerateUniqueName(true); + properties.afterPropertiesSet(); + assertThat(properties.determineUrl()).isEqualTo(properties.determineUrl()); + + DataSourceProperties properties2 = new DataSourceProperties(); + properties2.setGenerateUniqueName(true); + properties2.afterPropertiesSet(); + assertThat(properties.determineUrl()).isNotEqualTo(properties2.determineUrl()); + } + @Test public void determineUsername() throws Exception { DataSourceProperties properties = new DataSourceProperties(); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDataSourceConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDataSourceConfigurationTests.java index fb360aa9f27..c1b6cc28ada 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDataSourceConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDataSourceConfigurationTests.java @@ -16,10 +16,16 @@ package org.springframework.boot.autoconfigure.jdbc; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; + import javax.sql.DataSource; +import org.junit.After; import org.junit.Test; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -28,18 +34,64 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link EmbeddedDataSourceConfiguration}. * * @author Dave Syer + * @author Stephane Nicoll */ public class EmbeddedDataSourceConfigurationTests { private AnnotationConfigApplicationContext context; + @After + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + @Test - public void testDefaultEmbeddedDatabase() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EmbeddedDataSourceConfiguration.class); - this.context.refresh(); + public void defaultEmbeddedDatabase() { + this.context = load(); assertThat(this.context.getBean(DataSource.class)).isNotNull(); - this.context.close(); + } + + @Test + public void generateUniqueName() throws Exception { + this.context = load("spring.datasource.generate-unique-name=true"); + AnnotationConfigApplicationContext context2 = + load("spring.datasource.generate-unique-name=true"); + try { + DataSource dataSource = this.context.getBean(DataSource.class); + DataSource dataSource2 = context2.getBean(DataSource.class); + assertThat(getDatabaseName(dataSource)) + .isNotEqualTo(getDatabaseName(dataSource2)); + System.out.println(dataSource2); + } + finally { + context2.close(); + } + } + + private String getDatabaseName(DataSource dataSource) throws SQLException { + Connection connection = dataSource.getConnection(); + try { + ResultSet catalogs = connection.getMetaData().getCatalogs(); + if (catalogs.next()) { + return catalogs.getString(1); + } + else { + throw new IllegalStateException("Unable to get database name"); + } + } + finally { + connection.close(); + } + } + + private AnnotationConfigApplicationContext load(String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + ctx.register(EmbeddedDataSourceConfiguration.class); + ctx.refresh(); + return ctx; } } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index fbe93283bd6..2cc6db53d74 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -595,6 +595,7 @@ content into your application; rather pick only the properties that you need. spring.datasource.data-password= # Password of the database to execute DML scripts (if different). spring.datasource.dbcp2.*= # Commons DBCP2 specific settings spring.datasource.driver-class-name= # Fully qualified name of the JDBC driver. Auto-detected based on the URL by default. + spring.datasource.generate-unique-name=false # Generate a random datasource name. spring.datasource.hikari.*= # Hikari specific settings spring.datasource.initialize=true # Populate the database using 'data.sql'. spring.datasource.jmx-enabled=false # Enable JMX support (if provided by the underlying pool). diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 1ed899cae6e..1dff54fc34f 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2664,6 +2664,14 @@ http://hsqldb.org/[HSQL] and http://db.apache.org/derby/[Derby] databases. You d to provide any connection URLs, simply include a build dependency to the embedded database that you want to use. +[NOTE] +==== +If you are using this feature in your tests, you may notice that the same database is +reused by your whole test suite regardless of the number of application contexts that +you use. If you want to make sure that each context has a separate embedded database, +you should set `spring.datasource.generate-unique-name` to `true`. +==== + For example, typical POM dependencies would be: [source,xml,indent=0]