Skip to content

Commit 16111f1

Browse files
committed
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
1 parent c53d4f2 commit 16111f1

File tree

2 files changed

+48
-30
lines changed

2 files changed

+48
-30
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
2828
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
2929
import org.springframework.boot.actuate.health.Health;
3030
import org.springframework.boot.actuate.health.HealthIndicator;
31-
import org.springframework.boot.jdbc.DatabaseDriver;
31+
import org.springframework.boot.actuate.health.Status;
3232
import org.springframework.dao.support.DataAccessUtils;
3333
import org.springframework.jdbc.IncorrectResultSetColumnCountException;
3434
import org.springframework.jdbc.core.ConnectionCallback;
@@ -51,8 +51,6 @@
5151
*/
5252
public class DataSourceHealthIndicator extends AbstractHealthIndicator implements InitializingBean {
5353

54-
private static final String DEFAULT_QUERY = "SELECT 1";
55-
5654
private DataSource dataSource;
5755

5856
private String query;
@@ -104,17 +102,27 @@ protected void doHealthCheck(Health.Builder builder) throws Exception {
104102
}
105103

106104
private void doDataSourceHealthCheck(Health.Builder builder) throws Exception {
107-
String product = getProduct();
108-
builder.up().withDetail("database", product);
109-
String validationQuery = getValidationQuery(product);
110-
try {
111-
// Avoid calling getObject as it breaks MySQL on Java 7
112-
List<Object> results = this.jdbcTemplate.query(validationQuery, new SingleColumnRowMapper());
113-
Object result = DataAccessUtils.requiredSingleResult(results);
114-
builder.withDetail("result", result);
105+
builder.up().withDetail("database", getProduct());
106+
String validationQuery = this.query;
107+
if (StringUtils.hasText(validationQuery)) {
108+
try {
109+
// Avoid calling getObject as it breaks MySQL on Java 7
110+
List<Object> results = this.jdbcTemplate.query(validationQuery, new SingleColumnRowMapper());
111+
Object result = DataAccessUtils.requiredSingleResult(results);
112+
builder.withDetail("result", result);
113+
}
114+
finally {
115+
builder.withDetail("validationQuery", validationQuery);
116+
}
115117
}
116-
finally {
117-
builder.withDetail("validationQuery", validationQuery);
118+
else {
119+
try {
120+
boolean valid = isConnectionValid();
121+
builder.status((valid) ? Status.UP : Status.DOWN);
122+
}
123+
finally {
124+
builder.withDetail("validationQuery", "isValid()");
125+
}
118126
}
119127
}
120128

@@ -126,16 +134,12 @@ private String getProduct(Connection connection) throws SQLException {
126134
return connection.getMetaData().getDatabaseProductName();
127135
}
128136

129-
protected String getValidationQuery(String product) {
130-
String query = this.query;
131-
if (!StringUtils.hasText(query)) {
132-
DatabaseDriver specific = DatabaseDriver.fromProductName(product);
133-
query = specific.getValidationQuery();
134-
}
135-
if (!StringUtils.hasText(query)) {
136-
query = DEFAULT_QUERY;
137-
}
138-
return query;
137+
private Boolean isConnectionValid() {
138+
return this.jdbcTemplate.execute((ConnectionCallback<Boolean>) this::isConnectionValid);
139+
}
140+
141+
private Boolean isConnectionValid(Connection connection) throws SQLException {
142+
return connection.isValid(0);
139143
}
140144

141145
/**
@@ -149,8 +153,8 @@ public void setDataSource(DataSource dataSource) {
149153

150154
/**
151155
* Set a specific validation query to use to validate a connection. If none is set, a
152-
* default validation query is used.
153-
* @param query the query
156+
* validation based on {@link Connection#isValid(int)} is used.
157+
* @param query the validation query to use
154158
*/
155159
public void setQuery(String query) {
156160
this.query = query;

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicatorTests.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.actuate.jdbc;
1818

1919
import java.sql.Connection;
20+
import java.sql.SQLException;
2021

2122
import javax.sql.DataSource;
2223

@@ -26,7 +27,6 @@
2627

2728
import org.springframework.boot.actuate.health.Health;
2829
import org.springframework.boot.actuate.health.Status;
29-
import org.springframework.boot.jdbc.DatabaseDriver;
3030
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
3131
import org.springframework.jdbc.core.JdbcTemplate;
3232
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
@@ -69,8 +69,8 @@ void healthIndicatorWithDefaultSettings() {
6969
this.indicator.setDataSource(this.dataSource);
7070
Health health = this.indicator.health();
7171
assertThat(health.getStatus()).isEqualTo(Status.UP);
72-
assertThat(health.getDetails()).containsOnly(entry("database", "HSQL Database Engine"), entry("result", 1L),
73-
entry("validationQuery", DatabaseDriver.HSQLDB.getValidationQuery()));
72+
assertThat(health.getDetails()).containsOnly(entry("database", "HSQL Database Engine"),
73+
entry("validationQuery", "isValid()"));
7474
}
7575

7676
@Test
@@ -109,4 +109,18 @@ void healthIndicatorCloseConnection() throws Exception {
109109
verify(connection, times(2)).close();
110110
}
111111

112+
@Test
113+
void healthIndicatorWithConnectionValidationFailure() throws SQLException {
114+
DataSource dataSource = mock(DataSource.class);
115+
Connection connection = mock(Connection.class);
116+
given(connection.isValid(0)).willReturn(false);
117+
given(connection.getMetaData()).willReturn(this.dataSource.getConnection().getMetaData());
118+
given(dataSource.getConnection()).willReturn(connection);
119+
this.indicator.setDataSource(dataSource);
120+
Health health = this.indicator.health();
121+
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
122+
assertThat(health.getDetails()).containsOnly(entry("database", "HSQL Database Engine"),
123+
entry("validationQuery", "isValid()"));
124+
}
125+
112126
}

0 commit comments

Comments
 (0)