From 16111f126e707fb5064bab6150df7a77f54850ee Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 18 Feb 2020 14:59:06 +0100 Subject: [PATCH] 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 --- .../jdbc/DataSourceHealthIndicator.java | 56 ++++++++++--------- .../jdbc/DataSourceHealthIndicatorTests.java | 22 ++++++-- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java index 199e05277eb..81459c4f0ba 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java @@ -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"); * 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.Health; 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.jdbc.IncorrectResultSetColumnCountException; import org.springframework.jdbc.core.ConnectionCallback; @@ -51,8 +51,6 @@ import org.springframework.util.StringUtils; */ public class DataSourceHealthIndicator extends AbstractHealthIndicator implements InitializingBean { - private static final String DEFAULT_QUERY = "SELECT 1"; - private DataSource dataSource; private String query; @@ -104,17 +102,27 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement } private void doDataSourceHealthCheck(Health.Builder builder) throws Exception { - String product = getProduct(); - builder.up().withDetail("database", product); - String validationQuery = getValidationQuery(product); - try { - // Avoid calling getObject as it breaks MySQL on Java 7 - List results = this.jdbcTemplate.query(validationQuery, new SingleColumnRowMapper()); - Object result = DataAccessUtils.requiredSingleResult(results); - builder.withDetail("result", result); + builder.up().withDetail("database", getProduct()); + String validationQuery = this.query; + if (StringUtils.hasText(validationQuery)) { + try { + // Avoid calling getObject as it breaks MySQL on Java 7 + List results = this.jdbcTemplate.query(validationQuery, new SingleColumnRowMapper()); + Object result = DataAccessUtils.requiredSingleResult(results); + builder.withDetail("result", result); + } + finally { + builder.withDetail("validationQuery", validationQuery); + } } - finally { - builder.withDetail("validationQuery", validationQuery); + else { + 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(); } - protected String getValidationQuery(String product) { - String query = this.query; - if (!StringUtils.hasText(query)) { - DatabaseDriver specific = DatabaseDriver.fromProductName(product); - query = specific.getValidationQuery(); - } - if (!StringUtils.hasText(query)) { - query = DEFAULT_QUERY; - } - return query; + private Boolean isConnectionValid() { + return this.jdbcTemplate.execute((ConnectionCallback) this::isConnectionValid); + } + + private Boolean isConnectionValid(Connection connection) throws SQLException { + return connection.isValid(0); } /** @@ -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 - * default validation query is used. - * @param query the query + * validation based on {@link Connection#isValid(int)} is used. + * @param query the validation query to use */ public void setQuery(String query) { this.query = query; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicatorTests.java index 865d28d8aeb..6e7352e08f2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicatorTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.jdbc; import java.sql.Connection; +import java.sql.SQLException; 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.Status; -import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.SingleConnectionDataSource; @@ -69,8 +69,8 @@ class DataSourceHealthIndicatorTests { this.indicator.setDataSource(this.dataSource); Health health = this.indicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).containsOnly(entry("database", "HSQL Database Engine"), entry("result", 1L), - entry("validationQuery", DatabaseDriver.HSQLDB.getValidationQuery())); + assertThat(health.getDetails()).containsOnly(entry("database", "HSQL Database Engine"), + entry("validationQuery", "isValid()")); } @Test @@ -109,4 +109,18 @@ class DataSourceHealthIndicatorTests { 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()")); + } + }