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
This commit is contained in:
Stephane Nicoll
2016-02-10 18:01:04 +01:00
parent eb7b1ec33c
commit 1f106ddf8c
10 changed files with 339 additions and 272 deletions

View File

@@ -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;
/**

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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 extends DataSource> T testDataSourceFallback(Class<T> expectedType,
private <T extends DataSource> T autoConfigureDataSource(Class<T> 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 {

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2012-2014 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.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DatabaseDriver}.
*
* @author Phillip Webb
* @author Maciej Walkowiak
*/
public class DatabaseDriverTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void classNameForKnownDatabase() {
String driverClassName = DatabaseDriver
.fromJdbcUrl("jdbc:postgresql://hostname/dbname").getDriverClassName();
assertThat(driverClassName).isEqualTo("org.postgresql.Driver");
}
@Test
public void nullClassNameForUnknownDatabase() {
String driverClassName = DatabaseDriver
.fromJdbcUrl("jdbc:unknowndb://hostname/dbname").getDriverClassName();
assertThat(driverClassName).isNull();
}
@Test
public void unknownOnNullJdbcUrl() {
assertThat(DatabaseDriver.fromJdbcUrl(null)).isEqualTo(DatabaseDriver.UNKNOWN);
}
@Test
public void failureOnMalformedJdbcUrl() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("URL must start with");
DatabaseDriver.fromJdbcUrl("malformed:url");
}
}