From 1f106ddf8c585bc6716ff0144811415fa7b730ee Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 10 Feb 2016 18:01:04 +0100 Subject: [PATCH] Enable connection validation by default Hikari and Commons DBCP2 are already validating that the connection is valid before borrowing it from the pool. This commit makes that behaviour consistent by enabling that feature for the Tomcat and Commons DBCP data sources. Since a validation query is required in those cases, the infrastructure of `DataSourceHealthIndicator` has been merged in a single place: the `DatabaseDriver` enum provides not only the driver class names but also the validation query, if any. Closes gh-4906 --- .../health/DataSourceHealthIndicator.java | 79 +----- .../DataSourceHealthIndicatorTests.java | 19 +- .../autoconfigure/jdbc/DataSourceBuilder.java | 3 +- .../jdbc/DataSourceConfiguration.java | 21 +- .../jdbc/DataSourceProperties.java | 1 + .../autoconfigure/jdbc/DatabaseDriver.java | 163 ------------ .../jdbc/XADataSourceAutoConfiguration.java | 1 + .../DataSourceAutoConfigurationTests.java | 54 +++- .../boot/jdbc/DatabaseDriver.java | 241 ++++++++++++++++++ .../boot}/jdbc/DatabaseDriverTests.java | 29 ++- 10 files changed, 339 insertions(+), 272 deletions(-) delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jdbc/DatabaseDriver.java rename {spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure => spring-boot/src/test/java/org/springframework/boot}/jdbc/DatabaseDriverTests.java (51%) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DataSourceHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DataSourceHealthIndicator.java index 30f776e9d20..51b6e3353a3 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DataSourceHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DataSourceHealthIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import java.util.List; import javax.sql.DataSource; import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.IncorrectResultSetColumnCountException; @@ -131,10 +132,8 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator protected String getValidationQuery(String product) { String query = this.query; if (!StringUtils.hasText(query)) { - Product specific = Product.forProduct(product); - if (specific != null) { - query = specific.getQuery(); - } + DatabaseDriver specific = DatabaseDriver.fromProductName(product); + query = specific.getValidationQuery(); } if (!StringUtils.hasText(query)) { query = DEFAULT_QUERY; @@ -185,74 +184,4 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator } - /** - * Known database products. - */ - protected enum Product { - - HSQLDB("HSQL Database Engine", - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.SYSTEM_USERS"), - - ORACLE("Oracle", "SELECT 'Hello' from DUAL"), - - DERBY("Apache Derby", "SELECT 1 FROM SYSIBM.SYSDUMMY1"), - - DB2("DB2", "SELECT 1 FROM SYSIBM.SYSDUMMY1") { - - @Override - protected boolean matchesProduct(String product) { - return super.matchesProduct(product) - || product.toLowerCase().startsWith("db2/"); - } - - }, - - DB2_AS400("DB2 UDB for AS/400", "SELECT 1 FROM SYSIBM.SYSDUMMY1") { - @Override - protected boolean matchesProduct(String product) { - return super.matchesProduct(product) - || product.toLowerCase().contains("as/400"); - } - }, - - INFORMIX("Informix Dynamic Server", "select count(*) from systables"), - - FIREBIRD("Firebird", "SELECT 1 FROM RDB$DATABASE") { - - @Override - protected boolean matchesProduct(String product) { - return super.matchesProduct(product) - || product.toLowerCase().startsWith("firebird"); - } - - }; - - private final String product; - - private final String query; - - Product(String product, String query) { - this.product = product; - this.query = query; - } - - protected boolean matchesProduct(String product) { - return this.product.equalsIgnoreCase(product); - } - - public String getQuery() { - return this.query; - } - - public static Product forProduct(String product) { - for (Product candidate : values()) { - if (candidate.matchesProduct(product)) { - return candidate; - } - } - return null; - } - - } - } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DataSourceHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DataSourceHealthIndicatorTests.java index 1ac7811ae72..6fe7cde3df2 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DataSourceHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DataSourceHealthIndicatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.springframework.boot.actuate.health.DataSourceHealthIndicator.Product; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.SingleConnectionDataSource; @@ -104,20 +103,4 @@ public class DataSourceHealthIndicatorTests { verify(connection, times(2)).close(); } - @Test - public void productLookups() throws Exception { - assertThat(Product.forProduct("newone")).isNull(); - assertThat(Product.forProduct("HSQL Database Engine")).isEqualTo(Product.HSQLDB); - assertThat(Product.forProduct("Oracle")).isEqualTo(Product.ORACLE); - assertThat(Product.forProduct("Apache Derby")).isEqualTo(Product.DERBY); - assertThat(Product.forProduct("DB2")).isEqualTo(Product.DB2); - assertThat(Product.forProduct("DB2/LINUXX8664")).isEqualTo(Product.DB2); - assertThat(Product.forProduct("DB2 UDB for AS/400")).isEqualTo(Product.DB2_AS400); - assertThat(Product.forProduct("DB3 XDB for AS/400")).isEqualTo(Product.DB2_AS400); - assertThat(Product.forProduct("Informix Dynamic Server")) - .isEqualTo(Product.INFORMIX); - assertThat(Product.forProduct("Firebird 2.5.WI")).isEqualTo(Product.FIREBIRD); - assertThat(Product.forProduct("Firebird 2.1.LI")).isEqualTo(Product.FIREBIRD); - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java index e81eb99c432..f519bc5764c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import javax.sql.DataSource; import org.springframework.beans.BeanUtils; import org.springframework.beans.MutablePropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; +import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.util.ClassUtils; /** diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java index d24a5bcced3..9c53811ecfd 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import com.zaxxer.hikari.HikariDataSource; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.context.annotation.Bean; /** @@ -51,8 +52,15 @@ abstract class DataSourceConfiguration { @ConfigurationProperties("spring.datasource.tomcat") public org.apache.tomcat.jdbc.pool.DataSource dataSource( DataSourceProperties properties) { - return createDataSource(properties, + org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties, org.apache.tomcat.jdbc.pool.DataSource.class); + DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl()); + String validationQuery = databaseDriver.getValidationQuery(); + if (validationQuery != null) { + dataSource.setTestOnBorrow(true); + dataSource.setValidationQuery(validationQuery); + } + return dataSource; } } @@ -76,8 +84,15 @@ abstract class DataSourceConfiguration { @ConfigurationProperties("spring.datasource.dbcp") public org.apache.commons.dbcp.BasicDataSource dataSource( DataSourceProperties properties) { - return createDataSource(properties, + org.apache.commons.dbcp.BasicDataSource dataSource = createDataSource(properties, org.apache.commons.dbcp.BasicDataSource.class); + DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl()); + String validationQuery = databaseDriver.getValidationQuery(); + if (validationQuery != null) { + dataSource.setTestOnBorrow(true); + dataSource.setValidationQuery(validationQuery); + } + return dataSource; } } 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 ce70a9f9d98..0adaf1360ee 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 @@ -26,6 +26,7 @@ import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.util.Assert; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java deleted file mode 100644 index 030cbef3d8e..00000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.jdbc; - -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Enumeration of common database drivers. - * - * @author Phillip Webb - * @author Maciej Walkowiak - * @author Marten Deinum - * @since 1.2.0 - */ -enum DatabaseDriver { - - /** - * Unknown type. - */ - UNKNOWN(null), - - /** - * Apache Derby. - */ - DERBY("org.apache.derby.jdbc.EmbeddedDriver"), - - /** - * H2. - */ - H2("org.h2.Driver", "org.h2.jdbcx.JdbcDataSource"), - - /** - * HyperSQL DataBase. - */ - HSQLDB("org.hsqldb.jdbc.JDBCDriver", "org.hsqldb.jdbc.pool.JDBCXADataSource"), - - /** - * SQL Lite. - */ - SQLITE("org.sqlite.JDBC"), - - /** - * MySQL. - */ - MYSQL("com.mysql.jdbc.Driver", "com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"), - - /** - * Maria DB. - */ - MARIADB("org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MariaDbDataSource"), - - /** - * Google App Engine. - */ - GOOGLE("com.google.appengine.api.rdbms.AppEngineDriver"), - - /** - * Oracle. - */ - ORACLE("oracle.jdbc.OracleDriver", "oracle.jdbc.xa.client.OracleXADataSource"), - - /** - * Postgres. - */ - POSTGRESQL("org.postgresql.Driver", "org.postgresql.xa.PGXADataSource"), - - /** - * jTDS. - */ - JTDS("net.sourceforge.jtds.jdbc.Driver"), - - /** - * SQL Server. - */ - SQLSERVER("com.microsoft.sqlserver.jdbc.SQLServerDriver", - "com.microsoft.sqlserver.jdbc.SQLServerXADataSource"), - - /** - * Firebird. - */ - FIREBIRD("org.firebirdsql.jdbc.FBDriver", - "org.firebirdsql.pool.FBConnectionPoolDataSource"), - - /** - * DB2 Server. - */ - DB2("com.ibm.db2.jcc.DB2Driver", "com.ibm.db2.jcc.DB2XADataSource"), - - /** - * DB2 AS400 Server. - */ - AS400("com.ibm.as400.access.AS400JDBCDriver", - "com.ibm.as400.access.AS400JDBCXADataSource"), - - /** - * Teradata. - */ - TERADATA("com.teradata.jdbc.TeraDriver"); - - private final String driverClassName; - - private final String xaDataSourceClassName; - - DatabaseDriver(String driverClassName) { - this(driverClassName, null); - } - - DatabaseDriver(String driverClassName, String xaDataSourceClassName) { - this.driverClassName = driverClassName; - this.xaDataSourceClassName = xaDataSourceClassName; - } - - /** - * Return the driver class name. - * @return the class name or {@code null} - */ - public String getDriverClassName() { - return this.driverClassName; - } - - /** - * Return the XA driver source class name. - * @return the class name or {@code null} - */ - public String getXaDataSourceClassName() { - return this.xaDataSourceClassName; - } - - /** - * Find a {@link DatabaseDriver} for the given URL. - * @param url JDBC URL - * @return driver class name or {@link #UNKNOWN} if not found - */ - public static DatabaseDriver fromJdbcUrl(String url) { - if (StringUtils.hasLength(url)) { - Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'"); - String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(); - for (DatabaseDriver driver : values()) { - String prefix = ":" + driver.name().toLowerCase() + ":"; - if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) { - return driver; - } - } - } - return UNKNOWN; - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java index 9212695a07e..95dad6c4365 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jta.XADataSourceWrapper; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java index 89ae3b75a99..30b2540f0ee 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -114,27 +115,62 @@ public class DataSourceAutoConfigurationTests { } @Test - public void testHikariIsFallback() throws Exception { - HikariDataSource pool = testDataSourceFallback(HikariDataSource.class, + public void tomcatValidatesConnectionByDefault() { + org.apache.tomcat.jdbc.pool.DataSource dataSource = autoConfigureDataSource( + org.apache.tomcat.jdbc.pool.DataSource.class); + assertThat(dataSource.isTestOnBorrow()).isTrue(); + assertThat(dataSource.getValidationQuery()) + .isEqualTo(DatabaseDriver.HSQLDB.getValidationQuery()); + } + + @Test + public void hikariIsFallback() throws Exception { + HikariDataSource dataSource = autoConfigureDataSource(HikariDataSource.class, "org.apache.tomcat"); - assertThat(pool.getJdbcUrl()).isEqualTo("jdbc:hsqldb:mem:testdb"); + assertThat(dataSource.getJdbcUrl()).isEqualTo("jdbc:hsqldb:mem:testdb"); + } + + @Test + public void hikariValidatesConnectionByDefault() throws Exception { + HikariDataSource dataSource = autoConfigureDataSource(HikariDataSource.class, + "org.apache.tomcat"); + assertThat(dataSource.getConnectionTestQuery()).isNull(); // Use Connection#isValid() } @Test public void commonsDbcpIsFallback() throws Exception { - BasicDataSource dataSource = testDataSourceFallback(BasicDataSource.class, + BasicDataSource dataSource = autoConfigureDataSource(BasicDataSource.class, "org.apache.tomcat", "com.zaxxer.hikari"); assertThat(dataSource.getUrl()).isEqualTo("jdbc:hsqldb:mem:testdb"); } + @Test + public void commonsDbcpValidatesConnectionByDefault() { + BasicDataSource dataSource = autoConfigureDataSource(BasicDataSource.class, + "org.apache.tomcat", "com.zaxxer.hikari"); + assertThat(dataSource.getTestOnBorrow()).isTrue(); + assertThat(dataSource.getValidationQuery()) + .isEqualTo(DatabaseDriver.HSQLDB.getValidationQuery()); + } + @Test public void commonsDbcp2IsFallback() throws Exception { - org.apache.commons.dbcp2.BasicDataSource dataSource = testDataSourceFallback( + org.apache.commons.dbcp2.BasicDataSource dataSource = autoConfigureDataSource( org.apache.commons.dbcp2.BasicDataSource.class, "org.apache.tomcat", "com.zaxxer.hikari", "org.apache.commons.dbcp."); assertThat(dataSource.getUrl()).isEqualTo("jdbc:hsqldb:mem:testdb"); } + + @Test + public void commonsDbcp2ValidatesConnectionByDefault() throws Exception { + org.apache.commons.dbcp2.BasicDataSource dataSource = autoConfigureDataSource( + org.apache.commons.dbcp2.BasicDataSource.class, "org.apache.tomcat", + "com.zaxxer.hikari", "org.apache.commons.dbcp."); + assertThat(dataSource.getTestOnBorrow()).isEqualTo(true); + assertThat(dataSource.getValidationQuery()).isNull(); // Use Connection#isValid() + } + @Test public void testEmbeddedTypeDefaultsUsername() throws Exception { EnvironmentTestUtils.addEnvironment(this.context, @@ -169,7 +205,7 @@ public class DataSourceAutoConfigurationTests { EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.driverClassName:" + "org.springframework.boot.autoconfigure.jdbc." - + "DataSourceAutoConfigurationTests$DatabaseDriver", + + "DataSourceAutoConfigurationTests$DatabaseTestDriver", "spring.datasource.url:jdbc:foo://localhost"); this.context.register(DataSourceAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); @@ -178,7 +214,7 @@ public class DataSourceAutoConfigurationTests { assertThat(bean).isNotNull(); org.apache.tomcat.jdbc.pool.DataSource pool = (org.apache.tomcat.jdbc.pool.DataSource) bean; assertThat(pool.getDriverClassName()).isEqualTo( - "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfigurationTests$DatabaseDriver"); + "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfigurationTests$DatabaseTestDriver"); assertThat(pool.getUsername()).isNull(); } @@ -222,7 +258,7 @@ public class DataSourceAutoConfigurationTests { } @SuppressWarnings("unchecked") - private T testDataSourceFallback(Class expectedType, + private T autoConfigureDataSource(Class expectedType, final String... hiddenPackages) { EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.driverClassName:org.hsqldb.jdbcDriver", @@ -267,7 +303,7 @@ public class DataSourceAutoConfigurationTests { } // see testExplicitDriverClassClearsUserName - public static class DatabaseDriver implements Driver { + public static class DatabaseTestDriver implements Driver { @Override public Connection connect(String url, Properties info) throws SQLException { diff --git a/spring-boot/src/main/java/org/springframework/boot/jdbc/DatabaseDriver.java b/spring-boot/src/main/java/org/springframework/boot/jdbc/DatabaseDriver.java new file mode 100644 index 00000000000..744c5a568dd --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jdbc/DatabaseDriver.java @@ -0,0 +1,241 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.jdbc; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Enumeration of common database drivers. + * + * @author Phillip Webb + * @author Maciej Walkowiak + * @author Marten Deinum + * @author Stephane Nicoll + * @since 1.2.0 + */ +public enum DatabaseDriver { + + /** + * Unknown type. + */ + UNKNOWN(null, null), + + /** + * Apache Derby. + */ + DERBY("Apache Derby", "org.apache.derby.jdbc.EmbeddedDriver", null, + "SELECT 1 FROM SYSIBM.SYSDUMMY1"), + + /** + * H2. + */ + H2("H2", "org.h2.Driver", "org.h2.jdbcx.JdbcDataSource", "SELECT 1"), + + /** + * HyperSQL DataBase. + */ + HSQLDB("HSQL Database Engine", "org.hsqldb.jdbc.JDBCDriver", + "org.hsqldb.jdbc.pool.JDBCXADataSource", + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.SYSTEM_USERS"), + + /** + * SQL Lite. + */ + SQLITE("SQLite", "org.sqlite.JDBC"), + + /** + * MySQL. + */ + MYSQL("MySQL", "com.mysql.jdbc.Driver", + "com.mysql.jdbc.jdbc2.optional.MysqlXADataSource", "SELECT 1"), + + /** + * Maria DB. + */ + MARIADB("MySQL", "org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MariaDbDataSource", + "SELECT 1"), + + /** + * Google App Engine. + */ + GAE(null, "com.google.appengine.api.rdbms.AppEngineDriver"), + + /** + * Oracle. + */ + ORACLE("Oracle", "oracle.jdbc.OracleDriver", + "oracle.jdbc.xa.client.OracleXADataSource", "SELECT 'Hello' from DUAL"), + + /** + * Postgres. + */ + POSTGRESQL("PostgreSQL", "org.postgresql.Driver", "org.postgresql.xa.PGXADataSource", + "SELECT 1"), + + /** + * jTDS. As it can be used for several databases, there isn't a single product name + * we could rely on. + */ + JTDS(null, "net.sourceforge.jtds.jdbc.Driver"), + + /** + * SQL Server. + */ + SQLSERVER("SQL SERVER", "com.microsoft.sqlserver.jdbc.SQLServerDriver", + "com.microsoft.sqlserver.jdbc.SQLServerXADataSource", "SELECT 1"), + + /** + * Firebird. + */ + FIREBIRD("Firebird", "org.firebirdsql.jdbc.FBDriver", + "org.firebirdsql.pool.FBConnectionPoolDataSource", + "SELECT 1 FROM RDB$DATABASE") { + + @Override + protected boolean matchProductName(String productName) { + return super.matchProductName(productName) + || productName.toLowerCase().startsWith("firebird"); + } + }, + + /** + * DB2 Server. + */ + DB2("DB2", "com.ibm.db2.jcc.DB2Driver", "com.ibm.db2.jcc.DB2XADataSource", + "SELECT 1 FROM SYSIBM.SYSDUMMY1") { + + @Override + protected boolean matchProductName(String productName) { + return super.matchProductName(productName) + || productName.toLowerCase().startsWith("db2/"); + } + }, + + /** + * DB2 AS400 Server. + */ + DB2_AS400("DB2 UDB for AS/400", "com.ibm.as400.access.AS400JDBCDriver", + "com.ibm.as400.access.AS400JDBCXADataSource", + "SELECT 1 FROM SYSIBM.SYSDUMMY1") { + + @Override + protected boolean matchProductName(String productName) { + return super.matchProductName(productName) + || productName.toLowerCase().contains("as/400"); + } + }, + + /** + * Teradata. + */ + TERADATA("Teradata", "com.teradata.jdbc.TeraDriver"), + + /** + * Informix. + */ + INFORMIX("Informix Dynamic Server", "com.informix.jdbc.IfxDriver", null, + "select count(*) from systables"); + + private final String productName; + + private final String driverClassName; + + private final String xaDataSourceClassName; + + private final String validationQuery; + + DatabaseDriver(String name, String driverClassName) { + this(name, driverClassName, null); + } + + DatabaseDriver(String name, String driverClassName, String xaDataSourceClassName) { + this(name, driverClassName, xaDataSourceClassName, null); + } + + DatabaseDriver(String productName, String driverClassName, + String xaDataSourceClassName, String validationQuery) { + this.productName = productName; + this.driverClassName = driverClassName; + this.xaDataSourceClassName = xaDataSourceClassName; + this.validationQuery = validationQuery; + } + + protected boolean matchProductName(String productName) { + return this.productName != null && this.productName.equalsIgnoreCase(productName); + } + + /** + * Return the driver class name. + * @return the class name or {@code null} + */ + public String getDriverClassName() { + return this.driverClassName; + } + + /** + * Return the XA driver source class name. + * @return the class name or {@code null} + */ + public String getXaDataSourceClassName() { + return this.xaDataSourceClassName; + } + + /** + * Return the validation query. + * @return the validation query or {@code null} + */ + public String getValidationQuery() { + return this.validationQuery; + } + + /** + * Find a {@link DatabaseDriver} for the given URL. + * @param url JDBC URL + * @return the database driver or {@link #UNKNOWN} if not found + */ + public static DatabaseDriver fromJdbcUrl(String url) { + if (StringUtils.hasLength(url)) { + Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'"); + String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(); + for (DatabaseDriver driver : values()) { + String prefix = ":" + driver.name().toLowerCase() + ":"; + if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) { + return driver; + } + } + } + return UNKNOWN; + } + + /** + * Find a {@link DatabaseDriver} for the given product name. + * @param productName product name + * @return the database driver or {@link #UNKNOWN} if not found + */ + public static DatabaseDriver fromProductName(String productName) { + if (StringUtils.hasLength(productName)) { + for (DatabaseDriver candidate : values()) { + if (candidate.matchProductName(productName)) { + return candidate; + } + } + } + return UNKNOWN; + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java b/spring-boot/src/test/java/org/springframework/boot/jdbc/DatabaseDriverTests.java similarity index 51% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java rename to spring-boot/src/test/java/org/springframework/boot/jdbc/DatabaseDriverTests.java index 9f7ac2d2ecc..2a9dae91ec9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jdbc/DatabaseDriverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.jdbc; +package org.springframework.boot.jdbc; import org.junit.Rule; import org.junit.Test; @@ -49,7 +49,8 @@ public class DatabaseDriverTests { @Test public void unknownOnNullJdbcUrl() { - assertThat(DatabaseDriver.fromJdbcUrl(null)).isEqualTo(DatabaseDriver.UNKNOWN); + DatabaseDriver actual = DatabaseDriver.fromJdbcUrl(null); + assertThat(actual).isEqualTo(DatabaseDriver.UNKNOWN); } @Test @@ -59,4 +60,26 @@ public class DatabaseDriverTests { DatabaseDriver.fromJdbcUrl("malformed:url"); } + @Test + public void unknownOnNullProductName() { + DatabaseDriver actual = DatabaseDriver.fromProductName(null); + assertThat(actual).isEqualTo(DatabaseDriver.UNKNOWN); + } + + @Test + public void databaseProductNameLookups() throws Exception { + assertThat(DatabaseDriver.fromProductName("newone")).isEqualTo(DatabaseDriver.UNKNOWN); + assertThat(DatabaseDriver.fromProductName("HSQL Database Engine")).isEqualTo(DatabaseDriver.HSQLDB); + assertThat(DatabaseDriver.fromProductName("Oracle")).isEqualTo(DatabaseDriver.ORACLE); + assertThat(DatabaseDriver.fromProductName("Apache Derby")).isEqualTo(DatabaseDriver.DERBY); + assertThat(DatabaseDriver.fromProductName("DB2")).isEqualTo(DatabaseDriver.DB2); + assertThat(DatabaseDriver.fromProductName("DB2/LINUXX8664")).isEqualTo(DatabaseDriver.DB2); + assertThat(DatabaseDriver.fromProductName("DB2 UDB for AS/400")).isEqualTo(DatabaseDriver.DB2_AS400); + assertThat(DatabaseDriver.fromProductName("DB3 XDB for AS/400")).isEqualTo(DatabaseDriver.DB2_AS400); + assertThat(DatabaseDriver.fromProductName("Informix Dynamic Server")) + .isEqualTo(DatabaseDriver.INFORMIX); + assertThat(DatabaseDriver.fromProductName("Firebird 2.5.WI")).isEqualTo(DatabaseDriver.FIREBIRD); + assertThat(DatabaseDriver.fromProductName("Firebird 2.1.LI")).isEqualTo(DatabaseDriver.FIREBIRD); + } + }