001/* 002 * Copyright 2002-2017 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 javax.sql.DataSource; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.beans.factory.InitializingBean; 028import org.springframework.jdbc.CannotGetJdbcConnectionException; 029 030/** 031 * Bean that checks if a database has already started up. To be referenced 032 * via "depends-on" from beans that depend on database startup, like a Hibernate 033 * SessionFactory or custom data access objects that access a DataSource directly. 034 * 035 * <p>Useful to defer application initialization until a database has started up. 036 * Particularly appropriate for waiting on a slowly starting Oracle database. 037 * 038 * @author Juergen Hoeller 039 * @since 18.12.2003 040 */ 041public class DatabaseStartupValidator implements InitializingBean { 042 043 public static final int DEFAULT_INTERVAL = 1; 044 045 public static final int DEFAULT_TIMEOUT = 60; 046 047 048 protected final Log logger = LogFactory.getLog(getClass()); 049 050 private DataSource dataSource; 051 052 private String validationQuery; 053 054 private int interval = DEFAULT_INTERVAL; 055 056 private int timeout = DEFAULT_TIMEOUT; 057 058 059 /** 060 * Set the DataSource to validate. 061 */ 062 public void setDataSource(DataSource dataSource) { 063 this.dataSource = dataSource; 064 } 065 066 /** 067 * Set the SQL query string to use for validation. 068 */ 069 public void setValidationQuery(String validationQuery) { 070 this.validationQuery = validationQuery; 071 } 072 073 /** 074 * Set the interval between validation runs (in seconds). 075 * Default is {@value #DEFAULT_INTERVAL}. 076 */ 077 public void setInterval(int interval) { 078 this.interval = interval; 079 } 080 081 /** 082 * Set the timeout (in seconds) after which a fatal exception 083 * will be thrown. Default is {@value #DEFAULT_TIMEOUT}. 084 */ 085 public void setTimeout(int timeout) { 086 this.timeout = timeout; 087 } 088 089 090 /** 091 * Check whether the validation query can be executed on a Connection 092 * from the specified DataSource, with the specified interval between 093 * checks, until the specified timeout. 094 */ 095 @Override 096 public void afterPropertiesSet() { 097 DataSource dataSource = this.dataSource; 098 if (dataSource == null) { 099 throw new IllegalArgumentException("Property 'dataSource' is required"); 100 } 101 if (this.validationQuery == null) { 102 throw new IllegalArgumentException("Property 'validationQuery' is required"); 103 } 104 105 try { 106 boolean validated = false; 107 long beginTime = System.currentTimeMillis(); 108 long deadLine = beginTime + this.timeout * 1000; 109 SQLException latestEx = null; 110 111 while (!validated && System.currentTimeMillis() < deadLine) { 112 Connection con = null; 113 Statement stmt = null; 114 try { 115 con = dataSource.getConnection(); 116 stmt = con.createStatement(); 117 stmt.execute(this.validationQuery); 118 validated = true; 119 } 120 catch (SQLException ex) { 121 latestEx = ex; 122 if (logger.isDebugEnabled()) { 123 logger.debug("Validation query [" + this.validationQuery + "] threw exception", ex); 124 } 125 if (logger.isWarnEnabled()) { 126 float rest = ((float) (deadLine - System.currentTimeMillis())) / 1000; 127 if (rest > this.interval) { 128 logger.warn("Database has not started up yet - retrying in " + this.interval + 129 " seconds (timeout in " + rest + " seconds)"); 130 } 131 } 132 } 133 finally { 134 JdbcUtils.closeStatement(stmt); 135 JdbcUtils.closeConnection(con); 136 } 137 138 if (!validated) { 139 Thread.sleep(this.interval * 1000); 140 } 141 } 142 143 if (!validated) { 144 throw new CannotGetJdbcConnectionException( 145 "Database has not started up within " + this.timeout + " seconds", latestEx); 146 } 147 148 if (logger.isInfoEnabled()) { 149 float duration = ((float) (System.currentTimeMillis() - beginTime)) / 1000; 150 logger.info("Database startup detected after " + duration + " seconds"); 151 } 152 } 153 catch (InterruptedException ex) { 154 // Re-interrupt current thread, to allow other threads to react. 155 Thread.currentThread().interrupt(); 156 } 157 } 158 159}