From ab02084e7bc9bfa0702cecc65474216a64846c05 Mon Sep 17 00:00:00 2001 From: Asha Somayajula Date: Wed, 14 Oct 2020 18:36:58 -0500 Subject: [PATCH 1/2] Fix detection logic for embedded databases See gh-23693 --- .../jdbc/DataSourceProperties.java | 20 ++++----- .../flyway/FlywayAutoConfigurationTests.java | 1 - .../jdbc/DataSourcePropertiesTests.java | 45 ++++++++++++++++++- .../LiquibaseAutoConfigurationTests.java | 1 - .../jdbc/TestDatabaseAutoConfiguration.java | 2 +- .../boot/jdbc/EmbeddedDatabaseConnection.java | 15 +++++++ .../jdbc/EmbeddedDatabaseConnectionTests.java | 11 +++++ 7 files changed, 81 insertions(+), 14 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index adfe5bcfab4..64882c17ef4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -322,13 +322,13 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @since 1.4.0 */ public String determineUsername() { - if (StringUtils.hasText(this.username)) { - return this.username; - } - if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) { + if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl()) + && !StringUtils.hasText(this.username)) { return "sa"; } - return null; + else { + return this.username; + } } /** @@ -350,13 +350,13 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @since 1.4.0 */ public String determinePassword() { - if (StringUtils.hasText(this.password)) { - return this.password; - } - if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) { + if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl()) + && !StringUtils.hasText(this.password)) { return ""; } - return null; + else { + return this.password; + } } public String getJndiName() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 78be54d7a8c..456b1e533af 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -127,7 +127,6 @@ class FlywayAutoConfigurationTests { assertThat(context).hasSingleBean(Flyway.class); DataSource dataSource = context.getBean(Flyway.class).getConfiguration().getDataSource(); assertThat(dataSource).isNotNull(); - assertThat(dataSource).hasFieldOrPropertyWithValue("user", "sa"); assertThat(dataSource).hasFieldOrPropertyWithValue("password", ""); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java index 6f47aecb955..63214ce5143 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java @@ -78,6 +78,32 @@ class DataSourcePropertiesTests { assertThat(properties.determineUrl()).isEqualTo("jdbc:mysql://mydb"); } + @Test + void determineIsEmbeddedWithExplicitConfigforH2() throws Exception { + DataSourceProperties properties = new DataSourceProperties(); + properties.setUrl("jdbc:h2:~/test"); + properties.setUsername(""); + properties.setPassword(""); + properties.afterPropertiesSet(); + assertThat(properties.getUrl()).isEqualTo("jdbc:h2:~/test"); + assertThat(properties.determineUrl()).isEqualTo("jdbc:h2:~/test"); + assertThat(properties.determineUsername()).isEqualTo(""); + assertThat(properties.determinePassword()).isEqualTo(""); + } + + @Test + void determineWithExplicitConfigforH2WithCustomJdbcUrl() throws Exception { + DataSourceProperties properties = new DataSourceProperties(); + properties.setUrl("jdbc:h2:~/test"); + properties.setUsername("as"); + properties.setPassword("as"); + properties.afterPropertiesSet(); + assertThat(properties.getUrl()).isEqualTo("jdbc:h2:~/test"); + assertThat(properties.determineUrl()).isEqualTo("jdbc:h2:~/test"); + assertThat(properties.determineUsername()).isEqualTo("as"); + assertThat(properties.determinePassword()).isEqualTo("as"); + } + @Test void determineUrlWithGenerateUniqueName() throws Exception { DataSourceProperties properties = new DataSourceProperties(); @@ -98,6 +124,24 @@ class DataSourcePropertiesTests { assertThat(properties.determineUsername()).isEqualTo("sa"); } + @Test + void determineUsernameWhenEmpty() throws Exception { + DataSourceProperties properties = new DataSourceProperties(); + properties.setUsername(""); + properties.afterPropertiesSet(); + assertThat(properties.getUsername()); + assertThat(properties.determineUsername()).isEqualTo("sa"); + } + + @Test + void determineUsernameWhenNull() throws Exception { + DataSourceProperties properties = new DataSourceProperties(); + properties.setUsername(null); + properties.afterPropertiesSet(); + assertThat(properties.getUsername()); + assertThat(properties.determineUsername()).isEqualTo("sa"); + } + @Test void determineUsernameWithExplicitConfig() throws Exception { DataSourceProperties properties = new DataSourceProperties(); @@ -112,7 +156,6 @@ class DataSourcePropertiesTests { DataSourceProperties properties = new DataSourceProperties(); properties.afterPropertiesSet(); assertThat(properties.getPassword()).isNull(); - assertThat(properties.determinePassword()).isEqualTo(""); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index d8190d03c9d..fff97a917f4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -246,7 +246,6 @@ class LiquibaseAutoConfigurationTests { .run(assertLiquibase((liquibase) -> { DataSource dataSource = liquibase.getDataSource(); assertThat(((HikariDataSource) dataSource).isClosed()).isTrue(); - assertThat(((HikariDataSource) dataSource).getUsername()).isEqualTo("sa"); assertThat(((HikariDataSource) dataSource).getPassword()).isEqualTo(""); })); } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java index bf5183206d0..f5bfb4cc65e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java @@ -101,7 +101,7 @@ public class TestDatabaseAutoConfiguration { private BeanDefinition createEmbeddedBeanDefinition(boolean primary) { BeanDefinition beanDefinition = new RootBeanDefinition(EmbeddedDataSourceFactoryBean.class); - beanDefinition.setPrimary(primary); + beanDefinition.setPrimary(true); return beanDefinition; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java index d879aa9ab21..09e896e887d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java @@ -122,11 +122,26 @@ public enum EmbeddedDatabaseConnection { * @param driverClass the driver class * @return true if the driver class is one of the embedded types */ + @Deprecated public static boolean isEmbedded(String driverClass) { return driverClass != null && (matches(HSQL, driverClass) || matches(H2, driverClass) || matches(DERBY, driverClass) || matches(HSQLDB, driverClass)); } + /** + * Convenience method to determine if a given driver class name and url represents an + * embedded database type.The exception is made for the H2 database for embedded + * types. + * @param driverClass the driver class + * @param url the jdbc url + * @return true if the driver class is one of the embedded types + */ + public static boolean isEmbedded(String driverClass, String url) { + return (driverClass != null + && (matches(HSQL, driverClass) || (matches(H2, driverClass) && url.contains(":h2:mem")) + || matches(DERBY, driverClass) || matches(HSQLDB, driverClass))); + } + private static boolean matches(EmbeddedDatabaseConnection candidate, String driverClass) { return driverClass.equals(candidate.driverClass) || driverClass.equals(candidate.alternativeDriverClass); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnectionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnectionTests.java index 9155d293fe5..c0a2961add5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnectionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnectionTests.java @@ -78,4 +78,15 @@ class EmbeddedDatabaseConnectionTests { .withMessageContaining("DatabaseName must not be empty"); } + @Test + void isEmbeddedForh2CustomDatabaseName() { + assertThat(EmbeddedDatabaseConnection.isEmbedded("org.h2.Driver", "jdbc:h2:~/test")).isFalse(); + } + + @Test + void isEmbeddedForh2EmbeddedDatabaseName() { + assertThat(EmbeddedDatabaseConnection.isEmbedded("org.h2.Driver", + "jdbc:h2:mem:b3c7d078-1362-4be7-a088-e25dcc3aee32;DB_CLOSE_DELAY=-1")).isTrue(); + } + } From c0b267feb4f2905572f5735ed416e9caf896e462 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 16 Oct 2020 11:15:28 +0200 Subject: [PATCH 2/2] Polish "Fix detection logic for embedded databases" See gh-23693 --- .../jdbc/DataSourceProperties.java | 20 ++--- .../flyway/FlywayAutoConfigurationTests.java | 1 + .../jdbc/DataSourcePropertiesTests.java | 45 +++++------ .../LiquibaseAutoConfigurationTests.java | 1 + .../src/docs/asciidoc/howto.adoc | 4 +- .../jdbc/TestDatabaseAutoConfiguration.java | 2 +- .../boot/jdbc/EmbeddedDatabaseConnection.java | 61 +++++++++------ .../jdbc/EmbeddedDatabaseConnectionTests.java | 76 +++++++++++++++++-- 8 files changed, 143 insertions(+), 67 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 64882c17ef4..4c446836595 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -322,13 +322,13 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @since 1.4.0 */ public String determineUsername() { - if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl()) - && !StringUtils.hasText(this.username)) { - return "sa"; - } - else { + if (StringUtils.hasText(this.username)) { return this.username; } + if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl())) { + return "sa"; + } + return null; } /** @@ -350,13 +350,13 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @since 1.4.0 */ public String determinePassword() { - if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl()) - && !StringUtils.hasText(this.password)) { - return ""; - } - else { + if (StringUtils.hasText(this.password)) { return this.password; } + if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl())) { + return ""; + } + return null; } public String getJndiName() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 456b1e533af..78be54d7a8c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -127,6 +127,7 @@ class FlywayAutoConfigurationTests { assertThat(context).hasSingleBean(Flyway.class); DataSource dataSource = context.getBean(Flyway.class).getConfiguration().getDataSource(); assertThat(dataSource).isNotNull(); + assertThat(dataSource).hasFieldOrPropertyWithValue("user", "sa"); assertThat(dataSource).hasFieldOrPropertyWithValue("password", ""); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java index 63214ce5143..0195c23d9b5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java @@ -78,32 +78,6 @@ class DataSourcePropertiesTests { assertThat(properties.determineUrl()).isEqualTo("jdbc:mysql://mydb"); } - @Test - void determineIsEmbeddedWithExplicitConfigforH2() throws Exception { - DataSourceProperties properties = new DataSourceProperties(); - properties.setUrl("jdbc:h2:~/test"); - properties.setUsername(""); - properties.setPassword(""); - properties.afterPropertiesSet(); - assertThat(properties.getUrl()).isEqualTo("jdbc:h2:~/test"); - assertThat(properties.determineUrl()).isEqualTo("jdbc:h2:~/test"); - assertThat(properties.determineUsername()).isEqualTo(""); - assertThat(properties.determinePassword()).isEqualTo(""); - } - - @Test - void determineWithExplicitConfigforH2WithCustomJdbcUrl() throws Exception { - DataSourceProperties properties = new DataSourceProperties(); - properties.setUrl("jdbc:h2:~/test"); - properties.setUsername("as"); - properties.setPassword("as"); - properties.afterPropertiesSet(); - assertThat(properties.getUrl()).isEqualTo("jdbc:h2:~/test"); - assertThat(properties.determineUrl()).isEqualTo("jdbc:h2:~/test"); - assertThat(properties.determineUsername()).isEqualTo("as"); - assertThat(properties.determinePassword()).isEqualTo("as"); - } - @Test void determineUrlWithGenerateUniqueName() throws Exception { DataSourceProperties properties = new DataSourceProperties(); @@ -151,11 +125,21 @@ class DataSourcePropertiesTests { assertThat(properties.determineUsername()).isEqualTo("foo"); } + @Test + void determineUsernameWithNonEmbeddedUrl() throws Exception { + DataSourceProperties properties = new DataSourceProperties(); + properties.setUrl("jdbc:h2:~/test"); + properties.afterPropertiesSet(); + assertThat(properties.getPassword()).isNull(); + assertThat(properties.determineUsername()).isNull(); + } + @Test void determinePassword() throws Exception { DataSourceProperties properties = new DataSourceProperties(); properties.afterPropertiesSet(); assertThat(properties.getPassword()).isNull(); + assertThat(properties.determinePassword()).isEqualTo(""); } @Test @@ -167,6 +151,15 @@ class DataSourcePropertiesTests { assertThat(properties.determinePassword()).isEqualTo("bar"); } + @Test + void determinePasswordWithNonEmbeddedUrl() throws Exception { + DataSourceProperties properties = new DataSourceProperties(); + properties.setUrl("jdbc:h2:~/test"); + properties.afterPropertiesSet(); + assertThat(properties.getPassword()).isNull(); + assertThat(properties.determinePassword()).isNull(); + } + @Test void determineCredentialsForSchemaScripts() { DataSourceProperties properties = new DataSourceProperties(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index fff97a917f4..d8190d03c9d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -246,6 +246,7 @@ class LiquibaseAutoConfigurationTests { .run(assertLiquibase((liquibase) -> { DataSource dataSource = liquibase.getDataSource(); assertThat(((HikariDataSource) dataSource).isClosed()).isTrue(); + assertThat(((HikariDataSource) dataSource).getUsername()).isEqualTo("sa"); assertThat(((HikariDataSource) dataSource).getPassword()).isEqualTo(""); })); } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc index 2a4f621a9b1..9dde2a132ef 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc @@ -1982,8 +1982,8 @@ This is controlled through two external properties: You can set `spring.jpa.hibernate.ddl-auto` explicitly and the standard Hibernate property values are `none`, `validate`, `update`, `create`, and `create-drop`. Spring Boot chooses a default value for you based on whether it thinks your database is embedded. It defaults to `create-drop` if no schema manager has been detected or `none` in all other cases. -An embedded database is detected by looking at the `Connection` type. -`hsqldb`, `h2`, and `derby` are embedded, and others are not. +An embedded database is detected by looking at the `Connection` type and JDBC url. +`hsqldb`, `h2`, and `derby` are candidates, and others are not. Be careful when switching from in-memory to a '`real`' database that you do not make assumptions about the existence of the tables and data in the new platform. You either have to set `ddl-auto` explicitly or use one of the other mechanisms to initialize the database. diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java index f5bfb4cc65e..bf5183206d0 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/jdbc/TestDatabaseAutoConfiguration.java @@ -101,7 +101,7 @@ public class TestDatabaseAutoConfiguration { private BeanDefinition createEmbeddedBeanDefinition(boolean primary) { BeanDefinition beanDefinition = new RootBeanDefinition(EmbeddedDataSourceFactoryBean.class); - beanDefinition.setPrimary(true); + beanDefinition.setPrimary(primary); return beanDefinition; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java index 09e896e887d..1505c5af450 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java @@ -17,8 +17,11 @@ package org.springframework.boot.jdbc; import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.Locale; +import java.util.function.Predicate; +import java.util.stream.Stream; import javax.sql.DataSource; @@ -44,18 +47,19 @@ public enum EmbeddedDatabaseConnection { /** * No Connection. */ - NONE(null, null, null), + NONE(null, null, null, (url) -> false), /** * H2 Database Connection. */ H2(EmbeddedDatabaseType.H2, DatabaseDriver.H2.getDriverClassName(), - "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"), + "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE", (url) -> url.contains(":h2:mem")), /** * Derby Database Connection. */ - DERBY(EmbeddedDatabaseType.DERBY, DatabaseDriver.DERBY.getDriverClassName(), "jdbc:derby:memory:%s;create=true"), + DERBY(EmbeddedDatabaseType.DERBY, DatabaseDriver.DERBY.getDriverClassName(), "jdbc:derby:memory:%s;create=true", + (url) -> true), /** * HSQL Database Connection. @@ -63,13 +67,13 @@ public enum EmbeddedDatabaseConnection { */ @Deprecated HSQL(EmbeddedDatabaseType.HSQL, DatabaseDriver.HSQLDB.getDriverClassName(), "org.hsqldb.jdbcDriver", - "jdbc:hsqldb:mem:%s"), + "jdbc:hsqldb:mem:%s", (url) -> url.contains(":hsqldb:mem:")), /** * HSQL Database Connection. */ HSQLDB(EmbeddedDatabaseType.HSQL, DatabaseDriver.HSQLDB.getDriverClassName(), "org.hsqldb.jdbcDriver", - "jdbc:hsqldb:mem:%s"); + "jdbc:hsqldb:mem:%s", (url) -> url.contains(":hsqldb:mem:")); private final EmbeddedDatabaseType type; @@ -79,15 +83,20 @@ public enum EmbeddedDatabaseConnection { private final String url; - EmbeddedDatabaseConnection(EmbeddedDatabaseType type, String driverClass, String url) { - this(type, driverClass, null, url); + private final Predicate embeddedUrl; + + EmbeddedDatabaseConnection(EmbeddedDatabaseType type, String driverClass, String url, + Predicate embeddedUrl) { + this(type, driverClass, null, url, embeddedUrl); } - EmbeddedDatabaseConnection(EmbeddedDatabaseType type, String driverClass, String fallbackDriverClass, String url) { + EmbeddedDatabaseConnection(EmbeddedDatabaseType type, String driverClass, String fallbackDriverClass, String url, + Predicate embeddedUrl) { this.type = type; this.driverClass = driverClass; this.alternativeDriverClass = fallbackDriverClass; this.url = url; + this.embeddedUrl = embeddedUrl; } /** @@ -116,34 +125,41 @@ public enum EmbeddedDatabaseConnection { return (this.url != null) ? String.format(this.url, databaseName) : null; } + boolean isEmbeddedUrl(String url) { + return this.embeddedUrl.test(url); + } + + boolean isDriverCompatible(String driverClass) { + return (driverClass != null + && (driverClass.equals(this.driverClass) || driverClass.equals(this.alternativeDriverClass))); + } + /** * Convenience method to determine if a given driver class name represents an embedded * database type. * @param driverClass the driver class * @return true if the driver class is one of the embedded types + * @deprecated since 2.4.0 in favor of {@link #isEmbedded(String, String)} */ @Deprecated public static boolean isEmbedded(String driverClass) { - return driverClass != null && (matches(HSQL, driverClass) || matches(H2, driverClass) - || matches(DERBY, driverClass) || matches(HSQLDB, driverClass)); + return isEmbedded(driverClass, null); } /** - * Convenience method to determine if a given driver class name and url represents an - * embedded database type.The exception is made for the H2 database for embedded - * types. + * Convenience method to determine if a given driver class name and url represent an + * embedded database type. * @param driverClass the driver class - * @param url the jdbc url - * @return true if the driver class is one of the embedded types + * @param url the jdbc url (can be {@code null)} + * @return true if the driver class and url refer to an embedded database */ public static boolean isEmbedded(String driverClass, String url) { - return (driverClass != null - && (matches(HSQL, driverClass) || (matches(H2, driverClass) && url.contains(":h2:mem")) - || matches(DERBY, driverClass) || matches(HSQLDB, driverClass))); + return driverClass != null && getEmbeddedDatabaseConnection(driverClass).isEmbeddedUrl(url); } - private static boolean matches(EmbeddedDatabaseConnection candidate, String driverClass) { - return driverClass.equals(candidate.driverClass) || driverClass.equals(candidate.alternativeDriverClass); + private static EmbeddedDatabaseConnection getEmbeddedDatabaseConnection(String driverClass) { + return Stream.of(H2, HSQLDB, DERBY).filter((connection) -> connection.isDriverCompatible(driverClass)) + .findFirst().orElse(NONE); } /** @@ -184,7 +200,8 @@ public enum EmbeddedDatabaseConnection { @Override public Boolean doInConnection(Connection connection) throws SQLException, DataAccessException { - String productName = connection.getMetaData().getDatabaseProductName(); + DatabaseMetaData metaData = connection.getMetaData(); + String productName = metaData.getDatabaseProductName(); if (productName == null) { return false; } @@ -192,7 +209,7 @@ public enum EmbeddedDatabaseConnection { EmbeddedDatabaseConnection[] candidates = EmbeddedDatabaseConnection.values(); for (EmbeddedDatabaseConnection candidate : candidates) { if (candidate != NONE && productName.contains(candidate.name())) { - return true; + return candidate.isEmbeddedUrl(metaData.getURL()); } } return false; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnectionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnectionTests.java index c0a2961add5..e78591ab411 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnectionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnectionTests.java @@ -16,10 +16,24 @@ package org.springframework.boot.jdbc; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import javax.sql.DataSource; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; /** * Tests for {@link EmbeddedDatabaseConnection}. @@ -78,15 +92,65 @@ class EmbeddedDatabaseConnectionTests { .withMessageContaining("DatabaseName must not be empty"); } - @Test - void isEmbeddedForh2CustomDatabaseName() { - assertThat(EmbeddedDatabaseConnection.isEmbedded("org.h2.Driver", "jdbc:h2:~/test")).isFalse(); + @ParameterizedTest(name = "{1}") + @MethodSource("embeddedDriverAndUrlParameters") + void isEmbeddedWithDriverAndUrl(EmbeddedDatabaseConnection connection, String url, boolean embedded) { + assertThat(EmbeddedDatabaseConnection.isEmbedded(connection.getDriverClassName(), url)).isEqualTo(embedded); + } + + static Object[] embeddedDriverAndUrlParameters() { + return new Object[] { new Object[] { EmbeddedDatabaseConnection.H2, "jdbc:h2:~/test", false }, + new Object[] { EmbeddedDatabaseConnection.H2, "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", true }, + new Object[] { EmbeddedDatabaseConnection.HSQLDB, "jdbc:hsqldb:hsql://localhost", false }, + new Object[] { EmbeddedDatabaseConnection.HSQLDB, "jdbc:hsqldb:mem:test", true }, + new Object[] { EmbeddedDatabaseConnection.DERBY, "jdbc:derby:memory:test", true } }; } @Test - void isEmbeddedForh2EmbeddedDatabaseName() { - assertThat(EmbeddedDatabaseConnection.isEmbedded("org.h2.Driver", - "jdbc:h2:mem:b3c7d078-1362-4be7-a088-e25dcc3aee32;DB_CLOSE_DELAY=-1")).isTrue(); + void isEmbeddedWithH2DataSource() { + testEmbeddedDatabase(new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build()); + } + + @Test + void isEmbeddedWithHsqlDataSource() { + testEmbeddedDatabase(new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build()); + } + + @Test + void isEmbeddedWithDerbyDataSource() { + testEmbeddedDatabase(new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY).build()); + } + + void testEmbeddedDatabase(EmbeddedDatabase database) { + try { + assertThat(EmbeddedDatabaseConnection.isEmbedded(database)).isTrue(); + } + finally { + database.shutdown(); + } + } + + @Test + void isEmbeddedWithUnknownDataSource() throws SQLException { + assertThat(EmbeddedDatabaseConnection.isEmbedded(mockDataSource("unknown-db", null))).isFalse(); + } + + @Test + void isEmbeddedWithH2File() throws SQLException { + assertThat(EmbeddedDatabaseConnection + .isEmbedded(mockDataSource(EmbeddedDatabaseConnection.H2.getDriverClassName(), "jdbc:h2:~/test"))) + .isFalse(); + } + + DataSource mockDataSource(String productName, String connectionUrl) throws SQLException { + DatabaseMetaData metaData = mock(DatabaseMetaData.class); + given(metaData.getDatabaseProductName()).willReturn(productName); + given(metaData.getURL()).willReturn(connectionUrl); + Connection connection = mock(Connection.class); + given(connection.getMetaData()).willReturn(metaData); + DataSource dataSource = mock(DataSource.class); + given(dataSource.getConnection()).willReturn(connection); + return dataSource; } }