Use query-less datasource validation by default

This commit changes DataSourceHealthIndicator to validate the connection
rather than issuing a query to the database. If a custom validation
query is specified, it uses that as before.

Closes gh-17582
pull/20216/head
Stephane Nicoll 5 years ago
parent c53d4f2bf1
commit 16111f126e

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,7 +28,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.actuate.health.Status;
import org.springframework.dao.support.DataAccessUtils; import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.IncorrectResultSetColumnCountException; import org.springframework.jdbc.IncorrectResultSetColumnCountException;
import org.springframework.jdbc.core.ConnectionCallback; import org.springframework.jdbc.core.ConnectionCallback;
@ -51,8 +51,6 @@ import org.springframework.util.StringUtils;
*/ */
public class DataSourceHealthIndicator extends AbstractHealthIndicator implements InitializingBean { public class DataSourceHealthIndicator extends AbstractHealthIndicator implements InitializingBean {
private static final String DEFAULT_QUERY = "SELECT 1";
private DataSource dataSource; private DataSource dataSource;
private String query; private String query;
@ -104,17 +102,27 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
} }
private void doDataSourceHealthCheck(Health.Builder builder) throws Exception { private void doDataSourceHealthCheck(Health.Builder builder) throws Exception {
String product = getProduct(); builder.up().withDetail("database", getProduct());
builder.up().withDetail("database", product); String validationQuery = this.query;
String validationQuery = getValidationQuery(product); if (StringUtils.hasText(validationQuery)) {
try { try {
// Avoid calling getObject as it breaks MySQL on Java 7 // Avoid calling getObject as it breaks MySQL on Java 7
List<Object> results = this.jdbcTemplate.query(validationQuery, new SingleColumnRowMapper()); List<Object> results = this.jdbcTemplate.query(validationQuery, new SingleColumnRowMapper());
Object result = DataAccessUtils.requiredSingleResult(results); Object result = DataAccessUtils.requiredSingleResult(results);
builder.withDetail("result", result); builder.withDetail("result", result);
}
finally {
builder.withDetail("validationQuery", validationQuery);
}
} }
finally { else {
builder.withDetail("validationQuery", validationQuery); try {
boolean valid = isConnectionValid();
builder.status((valid) ? Status.UP : Status.DOWN);
}
finally {
builder.withDetail("validationQuery", "isValid()");
}
} }
} }
@ -126,16 +134,12 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
return connection.getMetaData().getDatabaseProductName(); return connection.getMetaData().getDatabaseProductName();
} }
protected String getValidationQuery(String product) { private Boolean isConnectionValid() {
String query = this.query; return this.jdbcTemplate.execute((ConnectionCallback<Boolean>) this::isConnectionValid);
if (!StringUtils.hasText(query)) { }
DatabaseDriver specific = DatabaseDriver.fromProductName(product);
query = specific.getValidationQuery(); private Boolean isConnectionValid(Connection connection) throws SQLException {
} return connection.isValid(0);
if (!StringUtils.hasText(query)) {
query = DEFAULT_QUERY;
}
return query;
} }
/** /**
@ -149,8 +153,8 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
/** /**
* Set a specific validation query to use to validate a connection. If none is set, a * Set a specific validation query to use to validate a connection. If none is set, a
* default validation query is used. * validation based on {@link Connection#isValid(int)} is used.
* @param query the query * @param query the validation query to use
*/ */
public void setQuery(String query) { public void setQuery(String query) {
this.query = query; this.query = query;

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.boot.actuate.jdbc; package org.springframework.boot.actuate.jdbc;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -26,7 +27,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.springframework.jdbc.datasource.SingleConnectionDataSource;
@ -69,8 +69,8 @@ class DataSourceHealthIndicatorTests {
this.indicator.setDataSource(this.dataSource); this.indicator.setDataSource(this.dataSource);
Health health = this.indicator.health(); Health health = this.indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsOnly(entry("database", "HSQL Database Engine"), entry("result", 1L), assertThat(health.getDetails()).containsOnly(entry("database", "HSQL Database Engine"),
entry("validationQuery", DatabaseDriver.HSQLDB.getValidationQuery())); entry("validationQuery", "isValid()"));
} }
@Test @Test
@ -109,4 +109,18 @@ class DataSourceHealthIndicatorTests {
verify(connection, times(2)).close(); verify(connection, times(2)).close();
} }
@Test
void healthIndicatorWithConnectionValidationFailure() throws SQLException {
DataSource dataSource = mock(DataSource.class);
Connection connection = mock(Connection.class);
given(connection.isValid(0)).willReturn(false);
given(connection.getMetaData()).willReturn(this.dataSource.getConnection().getMetaData());
given(dataSource.getConnection()).willReturn(connection);
this.indicator.setDataSource(dataSource);
Health health = this.indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails()).containsOnly(entry("database", "HSQL Database Engine"),
entry("validationQuery", "isValid()"));
}
} }

Loading…
Cancel
Save