From 8eedd9d5cc6357a71663232b9601410e0c1a9793 Mon Sep 17 00:00:00 2001 From: Marten Deinum Date: Tue, 23 Jun 2020 14:23:39 +0200 Subject: [PATCH] Use JDBC 4 API for connection validation With this commit use the JDBC 4.0 isValid method to validate the connection. This is favorable over a validation query. --- .../support/DatabaseStartupValidator.java | 32 +++-- .../DatabaseStartupValidatorTests.java | 131 ++++++++++++++++++ 2 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/support/DatabaseStartupValidatorTests.java diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseStartupValidator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseStartupValidator.java index cab49de32b4..b3e6aa1b437 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseStartupValidator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseStartupValidator.java @@ -59,7 +59,12 @@ public class DatabaseStartupValidator implements InitializingBean { @Nullable private DataSource dataSource; + /** + * The query used to validate the connection + * @deprecated in favor of JDBC 4.0 connection validation + */ @Nullable + @Deprecated private String validationQuery; private int interval = DEFAULT_INTERVAL; @@ -76,7 +81,10 @@ public class DatabaseStartupValidator implements InitializingBean { /** * Set the SQL query string to use for validation. + * + * @deprecated in favor of the JDBC 4.0 connection validation */ + @Deprecated public void setValidationQuery(String validationQuery) { this.validationQuery = validationQuery; } @@ -108,9 +116,6 @@ public class DatabaseStartupValidator implements InitializingBean { if (this.dataSource == null) { throw new IllegalArgumentException("Property 'dataSource' is required"); } - if (this.validationQuery == null) { - throw new IllegalArgumentException("Property 'validationQuery' is required"); - } try { boolean validated = false; @@ -124,18 +129,29 @@ public class DatabaseStartupValidator implements InitializingBean { try { con = this.dataSource.getConnection(); if (con == null) { - throw new CannotGetJdbcConnectionException("Failed to execute validation query: " + + throw new CannotGetJdbcConnectionException("Failed to execute validation: " + "DataSource returned null from getConnection(): " + this.dataSource); } - stmt = con.createStatement(); - stmt.execute(this.validationQuery); - validated = true; + if (this.validationQuery == null) { + validated = con.isValid(this.interval); + } + else { + stmt = con.createStatement(); + stmt.execute(this.validationQuery); + validated = true; + } } catch (SQLException ex) { latestEx = ex; if (logger.isDebugEnabled()) { - logger.debug("Validation query [" + this.validationQuery + "] threw exception", ex); + if (this.validationQuery != null) { + logger.debug("Validation query [" + this.validationQuery + "] threw exception", ex); + } + else { + logger.debug(" Validation threw exception", ex); + } } + if (logger.isInfoEnabled()) { float rest = ((float) (deadLine - System.currentTimeMillis())) / 1000; if (rest > this.interval) { diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/DatabaseStartupValidatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/DatabaseStartupValidatorTests.java new file mode 100644 index 00000000000..93dba6e5f6e --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/DatabaseStartupValidatorTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2003-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. + * You may obtain a copy of the License at + * + * https://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.jdbc.support; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Mock object based test for {@code DatabaseStartupValidator}. + * + * @author Marten Deinum, + */ +class DatabaseStartupValidatorTests { + + private Connection connection; + private DataSource dataSource; + + @BeforeEach + public void setUp() throws Exception { + connection = mock(Connection.class); + dataSource = mock(DataSource.class); + given(dataSource.getConnection()).willReturn(connection); + } + + @Test + public void properSetupForDataSource() { + DatabaseStartupValidator validator = new DatabaseStartupValidator(); + assertThatThrownBy(validator::afterPropertiesSet) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void shouldUseJdbc4IsValidByDefault() throws Exception { + given(connection.isValid(1)).willReturn(true); + DatabaseStartupValidator validator = new DatabaseStartupValidator(); + validator.setDataSource(dataSource); + validator.afterPropertiesSet(); + + verify(connection, times(1)).isValid(1); + verify(connection, times(1)).close(); + + } + + @Test + public void shouldCallValidatonTwiceWhenNotValid() throws Exception { + given(connection.isValid(1)).willReturn(false, true); + DatabaseStartupValidator validator = new DatabaseStartupValidator(); + validator.setDataSource(dataSource); + validator.afterPropertiesSet(); + + verify(connection, times(2)).isValid(1); + verify(connection, times(2)).close(); + + } + + @Test + public void shouldCallValidatonTwiceInCaseOfException() throws Exception { + given(connection.isValid(1)).willThrow(new SQLException("Test")).willReturn(true); + DatabaseStartupValidator validator = new DatabaseStartupValidator(); + validator.setDataSource(dataSource); + validator.afterPropertiesSet(); + + verify(connection, times(2)).isValid(1); + verify(connection, times(2)).close(); + + } + + @Test + public void useValidationQueryInsteadOfIsValid() throws Exception { + String validationQuery = "SELECT NOW() FROM DUAL"; + Statement statement = mock(Statement.class); + given(connection.createStatement()).willReturn(statement); + given(statement.execute(validationQuery)).willReturn(true); + + DatabaseStartupValidator validator = new DatabaseStartupValidator(); + validator.setDataSource(dataSource); + validator.setValidationQuery(validationQuery); + validator.afterPropertiesSet(); + + verify(connection, times(1)).createStatement(); + verify(statement, times(1)).execute(validationQuery); + verify(connection, times(1)).close(); + verify(statement, times(1)).close(); + } + + @Test + public void shouldExecuteValidatonTwiceOnError() throws Exception { + String validationQuery = "SELECT NOW() FROM DUAL"; + Statement statement = mock(Statement.class); + given(connection.createStatement()).willReturn(statement); + given(statement.execute(validationQuery)) + .willThrow(new SQLException("Test")) + .willReturn(true); + + DatabaseStartupValidator validator = new DatabaseStartupValidator(); + validator.setDataSource(dataSource); + validator.setValidationQuery(validationQuery); + validator.afterPropertiesSet(); + + verify(connection, times(2)).createStatement(); + verify(statement, times(2)).execute(validationQuery); + verify(connection, times(2)).close(); + verify(statement, times(2)).close(); + } +}