001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jdbc.support; 018 019import java.sql.Connection; 020import java.sql.SQLException; 021import java.sql.Statement; 022import java.util.concurrent.TimeUnit; 023 024import javax.sql.DataSource; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028 029import org.springframework.beans.factory.InitializingBean; 030import org.springframework.jdbc.CannotGetJdbcConnectionException; 031import org.springframework.lang.Nullable; 032 033/** 034 * Bean that checks if a database has already started up. To be referenced 035 * via "depends-on" from beans that depend on database startup, like a Hibernate 036 * SessionFactory or custom data access objects that access a DataSource directly. 037 * 038 * <p>Useful to defer application initialization until a database has started up. 039 * Particularly appropriate for waiting on a slowly starting Oracle database. 040 * 041 * @author Juergen Hoeller 042 * @since 18.12.2003 043 */ 044public class DatabaseStartupValidator implements InitializingBean { 045 046 /** 047 * The default interval. 048 */ 049 public static final int DEFAULT_INTERVAL = 1; 050 051 /** 052 * The default timeout. 053 */ 054 public static final int DEFAULT_TIMEOUT = 60; 055 056 057 protected final Log logger = LogFactory.getLog(getClass()); 058 059 @Nullable 060 private DataSource dataSource; 061 062 @Nullable 063 private String validationQuery; 064 065 private int interval = DEFAULT_INTERVAL; 066 067 private int timeout = DEFAULT_TIMEOUT; 068 069 070 /** 071 * Set the DataSource to validate. 072 */ 073 public void setDataSource(DataSource dataSource) { 074 this.dataSource = dataSource; 075 } 076 077 /** 078 * Set the SQL query string to use for validation. 079 */ 080 public void setValidationQuery(String validationQuery) { 081 this.validationQuery = validationQuery; 082 } 083 084 /** 085 * Set the interval between validation runs (in seconds). 086 * Default is {@value #DEFAULT_INTERVAL}. 087 */ 088 public void setInterval(int interval) { 089 this.interval = interval; 090 } 091 092 /** 093 * Set the timeout (in seconds) after which a fatal exception 094 * will be thrown. Default is {@value #DEFAULT_TIMEOUT}. 095 */ 096 public void setTimeout(int timeout) { 097 this.timeout = timeout; 098 } 099 100 101 /** 102 * Check whether the validation query can be executed on a Connection 103 * from the specified DataSource, with the specified interval between 104 * checks, until the specified timeout. 105 */ 106 @Override 107 public void afterPropertiesSet() { 108 if (this.dataSource == null) { 109 throw new IllegalArgumentException("Property 'dataSource' is required"); 110 } 111 if (this.validationQuery == null) { 112 throw new IllegalArgumentException("Property 'validationQuery' is required"); 113 } 114 115 try { 116 boolean validated = false; 117 long beginTime = System.currentTimeMillis(); 118 long deadLine = beginTime + TimeUnit.SECONDS.toMillis(this.timeout); 119 SQLException latestEx = null; 120 121 while (!validated && System.currentTimeMillis() < deadLine) { 122 Connection con = null; 123 Statement stmt = null; 124 try { 125 con = this.dataSource.getConnection(); 126 if (con == null) { 127 throw new CannotGetJdbcConnectionException("Failed to execute validation query: " + 128 "DataSource returned null from getConnection(): " + this.dataSource); 129 } 130 stmt = con.createStatement(); 131 stmt.execute(this.validationQuery); 132 validated = true; 133 } 134 catch (SQLException ex) { 135 latestEx = ex; 136 if (logger.isDebugEnabled()) { 137 logger.debug("Validation query [" + this.validationQuery + "] threw exception", ex); 138 } 139 if (logger.isInfoEnabled()) { 140 float rest = ((float) (deadLine - System.currentTimeMillis())) / 1000; 141 if (rest > this.interval) { 142 logger.info("Database has not started up yet - retrying in " + this.interval + 143 " seconds (timeout in " + rest + " seconds)"); 144 } 145 } 146 } 147 finally { 148 JdbcUtils.closeStatement(stmt); 149 JdbcUtils.closeConnection(con); 150 } 151 152 if (!validated) { 153 TimeUnit.SECONDS.sleep(this.interval); 154 } 155 } 156 157 if (!validated) { 158 throw new CannotGetJdbcConnectionException( 159 "Database has not started up within " + this.timeout + " seconds", latestEx); 160 } 161 162 if (logger.isInfoEnabled()) { 163 float duration = ((float) (System.currentTimeMillis() - beginTime)) / 1000; 164 logger.info("Database startup detected after " + duration + " seconds"); 165 } 166 } 167 catch (InterruptedException ex) { 168 // Re-interrupt current thread, to allow other threads to react. 169 Thread.currentThread().interrupt(); 170 } 171 } 172 173}