All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebeaninternal.server.lib.sql.PooledConnection Maven / Gradle / Ivy

The newest version!
/**
 *  Copyright (C) 2006  Robin Bygrave
 *  
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *  
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
package com.avaje.ebeaninternal.server.lib.sql;

import com.avaje.ebeaninternal.jdbc.ConnectionDelegator;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Is a connection that belongs to a DataSourcePool.
 * 
 * 

* It is designed to be part of DataSourcePool. Closing the connection puts it * back into the pool. *

* *

* It defaults autoCommit and Transaction Isolation to the defaults of the * DataSourcePool. *

* *

* It has caching of Statements and PreparedStatements. Remembers the last * statement that was executed. Keeps statistics on how long it is in use. *

*/ public class PooledConnection extends ConnectionDelegator { private static final Logger logger = Logger.getLogger(PooledConnection.class.getName()); private static String IDLE_CONNECTION_ACCESSED_ERROR = "Pooled Connection has been accessed whilst idle in the pool, via method: "; /** * Set when connection is idle in the pool. In general when in the pool the * connection should not be modified. */ static final int STATUS_IDLE = 88; /** * Set when connection given to client. */ static final int STATUS_ACTIVE = 89; /** * Set when commit() or rollback() called. */ static final int STATUS_ENDED = 87; /** * Name used to identify the PooledConnection for logging. */ final String name; /** * The pool this connection belongs to. */ final DataSourcePool pool; /** * The underlying connection. */ final Connection connection; /** * The time this connection was created. */ final long creationTime; /** * Cache of the PreparedStatements */ final PstmtCache pstmtCache; final Object pstmtMonitor = new Object(); /** * The status of the connection. IDLE, ACTIVE or ENDED. */ int status = STATUS_IDLE; /** * Set this to true if the connection will be busy for a long time. *

* This means it should skip the suspected connection pool leak checking. *

*/ boolean longRunning; /** * Flag to indicate that this connection had errors and should be checked to * make sure it is okay. */ boolean hadErrors; /** * The last start time. When the connection was given to a thread. */ long startUseTime; /** * The last end time of this connection. This is to calculate the usage * time. */ long lastUseTime; /** * The last statement executed by this connection. */ String lastStatement; /** * The number of hits against the preparedStatement cache. */ int pstmtHitCounter; /** * The number of misses against the preparedStatement cache. */ int pstmtMissCounter; /** * The non avaje method that created the connection. */ String createdByMethod; /** * Used to find connection pool leaks. */ StackTraceElement[] stackTrace; int maxStackTrace; /** * Slot position in the BusyConnectionBuffer. */ int slotId; /** * Construct the connection that can refer back to the pool it belongs to. *

* close() will return the connection back to the pool , while * closeDestroy() will close() the underlining connection properly. *

*/ public PooledConnection(DataSourcePool pool, int uniqueId, Connection connection) throws SQLException { super(connection); this.pool = pool; this.connection = connection; this.name = pool.getName() + "." + uniqueId; this.pstmtCache = new PstmtCache(name, pool.getPstmtCacheSize()); this.maxStackTrace = pool.getMaxStackTraceSize(); this.creationTime = System.currentTimeMillis(); this.lastUseTime = creationTime; } /** * For testing the pool without real connections. */ protected PooledConnection(String name) { super(null); this.name = name; this.pool = null; this.connection = null; this.pstmtCache = null; this.maxStackTrace = 0; this.creationTime = System.currentTimeMillis(); this.lastUseTime = creationTime; } /** * Return the slot position in the busy buffer. */ public int getSlotId() { return slotId; } /** * Set the slot position in the busy buffer. */ public void setSlotId(int slotId) { this.slotId = slotId; } /** * Return the DataSourcePool that this connection belongs to. */ public DataSourcePool getDataSourcePool() { return pool; } /** * Return the time the connection was created. */ public long getCreationTime() { return creationTime; } /** * Return a string to identify the connection. */ public String getName() { return name; } public String toString() { return name; } public String getDescription() { return "name["+name+"] startTime["+getStartUseTime()+"] stmt["+getLastStatement()+"] createdBy["+getCreatedByMethod()+"]"; } public String getStatistics() { return "name["+name+"] startTime["+getStartUseTime()+"] pstmtHits["+pstmtHitCounter+"] pstmtMiss["+pstmtMissCounter+"] "+pstmtCache.getDescription(); } /** * Return true if the connection should be treated as long running (skip connection pool leak check). */ public boolean isLongRunning() { return longRunning; } /** * Set this to true if the connection is a long running connection and should skip the * 'suspected connection pool leak' checking. */ public void setLongRunning(boolean longRunning) { this.longRunning = longRunning; } /** * Close the connection fully NOT putting in back into the pool. *

* The logErrors parameter exists so that expected errors are not logged * such as when the database is known to be down. *

* * @param logErrors * if false then don't log errors when closing */ public void closeConnectionFully(boolean logErrors) { //pool.removeConnection(this); String msg = "Closing Connection[" + getName() + "]" + " psReuse[" + pstmtHitCounter + "] psCreate[" + pstmtMissCounter + "] psSize[" + pstmtCache.size() + "]"; logger.info(msg); try { if (connection.isClosed()) { msg = "Closing Connection[" + getName() + "] that is already closed?"; logger.log(Level.SEVERE, msg); return; } } catch (SQLException ex) { if (logErrors) { msg = "Error when fully closing connection [" + getName() + "]"; logger.log(Level.SEVERE, msg, ex); } } try { Iterator psi = pstmtCache.values().iterator(); while (psi.hasNext()) { ExtendedPreparedStatement ps = (ExtendedPreparedStatement) psi.next(); ps.closeDestroy(); } } catch (SQLException ex) { if (logErrors) { logger.log(Level.WARNING, "Error when closing connection Statements", ex); } } try { connection.close(); } catch (SQLException ex) { if (logErrors) { msg = "Error when fully closing connection [" + getName() + "]"; logger.log(Level.SEVERE, msg, ex); } } } /** * A Least Recently used cache of PreparedStatements. */ public PstmtCache getPstmtCache() { return pstmtCache; } /** * Creates a wrapper ExtendedStatement so that I can get the executed sql. I * want to do this so that I can get the slowest query statments etc, and * log that information. */ public Statement createStatement() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()"); } try { return connection.createStatement(); } catch (SQLException ex) { addError(ex); throw ex; } } public Statement createStatement(int resultSetType, int resultSetConcurreny) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()"); } try { return connection.createStatement(resultSetType, resultSetConcurreny); } catch (SQLException ex) { addError(ex); throw ex; } } /** * Return a PreparedStatement back into the cache. */ protected void returnPreparedStatement(ExtendedPreparedStatement pstmt) { synchronized (pstmtMonitor) { ExtendedPreparedStatement alreadyInCache = pstmtCache.get(pstmt.getCacheKey()); if (alreadyInCache == null) { // add the returning prepared statement to the cache. // Note that the LRUCache will automatically close fully old unused // PStmts when the cache has hit its maximum size. pstmtCache.put(pstmt.getCacheKey(), pstmt); } else { try { // if a entry in the cache exists for the exact same SQL... // then remove it from the cache and close it fully. // Only having one PreparedStatement per unique SQL // statement pstmt.closeDestroy(); } catch (SQLException e) { logger.log(Level.SEVERE, "Error closing Pstmt", e); } } } } /** * This will try to use a cache of PreparedStatements. */ public PreparedStatement prepareStatement(String sql, int returnKeysFlag) throws SQLException { String cacheKey = sql + returnKeysFlag; return prepareStatement(sql, true, returnKeysFlag, cacheKey); } /** * This will try to use a cache of PreparedStatements. */ public PreparedStatement prepareStatement(String sql) throws SQLException { return prepareStatement(sql, false, 0, sql); } /** * This will try to use a cache of PreparedStatements. */ private PreparedStatement prepareStatement(String sql, boolean useFlag, int flag, String cacheKey) throws SQLException { if (status == STATUS_IDLE) { String m = IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"; throw new SQLException(m); } try { synchronized (pstmtMonitor) { lastStatement = sql; // try to get a matching cached PStmt from the cache. ExtendedPreparedStatement pstmt = pstmtCache.remove(cacheKey); if (pstmt != null) { pstmtHitCounter++; return pstmt; } // create a new PreparedStatement pstmtMissCounter++; PreparedStatement actualPstmt; if (useFlag) { actualPstmt = connection.prepareStatement(sql, flag); } else { actualPstmt = connection.prepareStatement(sql); } return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey); } } catch (SQLException ex) { addError(ex); throw ex; } } public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurreny) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"); } try { // no caching when creating PreparedStatements this way pstmtMissCounter++; lastStatement = sql; return connection.prepareStatement(sql, resultSetType, resultSetConcurreny); } catch (SQLException ex) { addError(ex); throw ex; } } /** * Reset the connection for returning to the client. Resets the status, * startUseTime and hadErrors. */ protected void resetForUse() { this.status = STATUS_ACTIVE; this.startUseTime = System.currentTimeMillis(); this.createdByMethod = null; this.lastStatement = null; this.hadErrors = false; this.longRunning = false; } /** * When an error occurs during use add it the connection. *

* Any PooledConnection that has an error is checked to make sure it works * before it is placed back into the connection pool. *

*/ public void addError(Throwable e) { hadErrors = true; } /** * Returns true if the connect threw any errors during use. *

* Connections with errors are testing to make sure they are still good * before putting them back into the pool. *

*/ public boolean hadErrors() { return hadErrors; } /** * close the connection putting it back into the connection pool. *

* Note that to ensure that the next transaction starts at the correct time * a commit() or rollback() should be called. If neither has occured at this * time then a rollback() is used (to end the transaction). *

*

* To close the connection fully use closeConnectionFully(). *

*/ public void close() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()"); } if (hadErrors) { if (!pool.validateConnection(this)) { // the connection is BAD, close it and test the pool closeConnectionFully(false); pool.checkDataSource(); return; } } try { // reset the autoCommit back if client code changed it if (connection.getAutoCommit() != pool.getAutoCommit()) { connection.setAutoCommit(pool.getAutoCommit()); } // Generally resetting Isolation level seems expensive. // Hence using resetIsolationReadOnlyRequired flag // performance reasons. if (resetIsolationReadOnlyRequired) { resetIsolationReadOnly(); resetIsolationReadOnlyRequired = false; } // the connection is assumed GOOD so put it back in the pool lastUseTime = System.currentTimeMillis(); // connection.clearWarnings(); status = STATUS_IDLE; pool.returnConnection(this); } catch (Exception ex) { // the connection is BAD, close it and test the pool closeConnectionFully(false); pool.checkDataSource(); } } private void resetIsolationReadOnly() throws SQLException { // reset the transaction isolation if the client code changed it if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) { connection.setTransactionIsolation(pool.getTransactionIsolation()); } // reset readonly to false if (connection.isReadOnly()) { connection.setReadOnly(false); } } protected void finalize() throws Throwable { try { if (connection != null && !connection.isClosed()) { // connect leak? String msg = "Closing Connection[" + getName() + "] on finalize()."; logger.warning(msg); closeConnectionFully(false); } } catch (Exception e) { logger.log(Level.SEVERE, null, e); } super.finalize(); } /** * Return the time the connection was passed to the client code. *

* Used to detect busy connections that could be leaks. *

*/ public long getStartUseTime() { return startUseTime; } /** * Returns the time the connection was last used. *

* Used to close connections that have been idle for some time. Typically 5 * minutes. *

*/ public long getLastUsedTime() { return lastUseTime; } /** * Returns the last sql statement executed. */ public String getLastStatement() { return lastStatement; } /** * Called by ExtendedStatement to trace the sql being executed. *

* Note with addBatch() this will not really work. *

*/ protected void setLastStatement(String lastStatement) { this.lastStatement = lastStatement; if (logger.isLoggable(Level.FINER)) { logger.finer(".setLastStatement[" + lastStatement + "]"); } } boolean resetIsolationReadOnlyRequired = false; /** * Also note the read only status needs to be reset when put back into the * pool. */ public void setReadOnly(boolean readOnly) throws SQLException { // A bit loose not checking for STATUS_IDLE // if (status == STATUS_IDLE) { // throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + // "setReadOnly()"); // } resetIsolationReadOnlyRequired = true; connection.setReadOnly(readOnly); } /** * Also note the Isolation level needs to be reset when put back into the * pool. */ public void setTransactionIsolation(int level) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTransactionIsolation()"); } try { resetIsolationReadOnlyRequired = true; connection.setTransactionIsolation(level); } catch (SQLException ex) { addError(ex); throw ex; } } // // // Simple wrapper methods which pass a method call onto the acutal // connection object. These methods are safe-guarded to prevent use of // the methods whilst the connection is in the connection pool. // // public void clearWarnings() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "clearWarnings()"); } connection.clearWarnings(); } public void commit() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "commit()"); } try { status = STATUS_ENDED; connection.commit(); } catch (SQLException ex) { addError(ex); throw ex; } } public boolean getAutoCommit() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getAutoCommit()"); } return connection.getAutoCommit(); } public String getCatalog() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getCatalog()"); } return connection.getCatalog(); } public DatabaseMetaData getMetaData() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getMetaData()"); } return connection.getMetaData(); } public int getTransactionIsolation() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTransactionIsolation()"); } return connection.getTransactionIsolation(); } public Map> getTypeMap() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTypeMap()"); } return connection.getTypeMap(); } public SQLWarning getWarnings() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getWarnings()"); } return connection.getWarnings(); } public boolean isClosed() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isClosed()"); } return connection.isClosed(); } public boolean isReadOnly() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isReadOnly()"); } return connection.isReadOnly(); } public String nativeSQL(String sql) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "nativeSQL()"); } lastStatement = sql; return connection.nativeSQL(sql); } public CallableStatement prepareCall(String sql) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()"); } lastStatement = sql; return connection.prepareCall(sql); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurreny) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()"); } lastStatement = sql; return connection.prepareCall(sql, resultSetType, resultSetConcurreny); } public void rollback() throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "rollback()"); } try { status = STATUS_ENDED; connection.rollback(); } catch (SQLException ex) { addError(ex); throw ex; } } public void setAutoCommit(boolean autoCommit) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setAutoCommit()"); } try { connection.setAutoCommit(autoCommit); } catch (SQLException ex) { addError(ex); throw ex; } } public void setCatalog(String catalog) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setCatalog()"); } connection.setCatalog(catalog); } public void setTypeMap(Map> map) throws SQLException { if (status == STATUS_IDLE) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTypeMap()"); } connection.setTypeMap(map); } public Savepoint setSavepoint() throws SQLException { try { return connection.setSavepoint(); } catch (SQLException ex) { addError(ex); throw ex; } } public Savepoint setSavepoint(String savepointName) throws SQLException { try { return connection.setSavepoint(savepointName); } catch (SQLException ex) { addError(ex); throw ex; } } public void rollback(Savepoint sp) throws SQLException { try { connection.rollback(sp); } catch (SQLException ex) { addError(ex); throw ex; } } public void releaseSavepoint(Savepoint sp) throws SQLException { try { connection.releaseSavepoint(sp); } catch (SQLException ex) { addError(ex); throw ex; } } public void setHoldability(int i) throws SQLException { try { connection.setHoldability(i); } catch (SQLException ex) { addError(ex); throw ex; } } public int getHoldability() throws SQLException { try { return connection.getHoldability(); } catch (SQLException ex) { addError(ex); throw ex; } } public Statement createStatement(int i, int x, int y) throws SQLException { try { return connection.createStatement(i, x, y); } catch (SQLException ex) { addError(ex); throw ex; } } public PreparedStatement prepareStatement(String s, int i, int x, int y) throws SQLException { try { return connection.prepareStatement(s, i, x, y); } catch (SQLException ex) { addError(ex); throw ex; } } public PreparedStatement prepareStatement(String s, int[] i) throws SQLException { try { return connection.prepareStatement(s, i); } catch (SQLException ex) { addError(ex); throw ex; } } public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException { try { return connection.prepareStatement(s, s2); } catch (SQLException ex) { addError(ex); throw ex; } } public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException { try { return connection.prepareCall(s, i, x, y); } catch (SQLException ex) { addError(ex); throw ex; } } /** * Returns the method that created the connection. *

* Used to help finding connection pool leaks. *

*/ public String getCreatedByMethod() { if (createdByMethod != null) { return createdByMethod; } if (stackTrace == null) { return null; } for (int j = 0; j < stackTrace.length; j++) { String methodLine = stackTrace[j].toString(); if (skipElement(methodLine)) { // ignore these methods... } else { createdByMethod = methodLine; return createdByMethod; } } return null; } private boolean skipElement(String methodLine) { if (methodLine.startsWith("java.lang.")) { return true; } else if (methodLine.startsWith("java.util.")) { return true; } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.CallableQuery.")) { // creating connection on future... return true; } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.Callable")) { // it is a future task being executed... return false; } else if (methodLine.startsWith("com.avaje.ebeaninternal")) { return true; } else { return false; } } /** * Set the stack trace to help find connection pool leaks. */ protected void setStackTrace(StackTraceElement[] stackTrace) { this.stackTrace = stackTrace; } /** * Return the full stack trace that got the connection from the pool. You * could use this if getCreatedByMethod() doesn't work for you. */ public StackTraceElement[] getStackTrace() { if (stackTrace == null){ return null; } // filter off the top of the stack that we are not interested in ArrayList filteredList = new ArrayList(); boolean include = false; for (int i = 0; i < stackTrace.length; i++) { if (!include && !skipElement(stackTrace[i].toString())){ include = true; } if (include && filteredList.size() < maxStackTrace){ filteredList.add(stackTrace[i]); } } return filteredList.toArray(new StackTraceElement[filteredList.size()]); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy