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

io.ebean.datasource.pool.PooledConnection Maven / Gradle / Ivy

package io.ebean.datasource.pool;

import io.ebean.datasource.delegate.ConnectionDelegator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.Arrays;
import java.util.Map;

/**
 * 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 = LoggerFactory.getLogger(PooledConnection.class); private static final String IDLE_CONNECTION_ACCESSED_ERROR = "Pooled Connection has been accessed whilst idle in the pool, via method: "; /** * Marker for when connection is closed due to exceeding the max allowed age. */ private static final String REASON_MAXAGE = "maxAge"; /** * Marker for when connection is closed due to exceeding the max inactive time. */ private static final String REASON_IDLE = "idleTime"; /** * Marker for when the connection is closed due to a reset. */ private static final String REASON_RESET = "reset"; /** * Set when connection is idle in the pool. In general when in the pool the * connection should not be modified. */ private static final int STATUS_IDLE = 88; /** * Set when connection given to client. */ private static final int STATUS_ACTIVE = 89; /** * Set when commit() or rollback() called. */ private static final int STATUS_ENDED = 87; /** * Name used to identify the PooledConnection for logging. */ private final String name; /** * The pool this connection belongs to. */ private final ConnectionPool pool; /** * The underlying connection. */ private final Connection connection; /** * The time this connection was created. */ private final long creationTime; /** * Cache of the PreparedStatements */ private final PstmtCache pstmtCache; private final Object pstmtMonitor = new Object(); /** * Helper for statistics collection. */ private final PooledConnectionStatistics stats = new PooledConnectionStatistics(); /** * The status of the connection. IDLE, ACTIVE or ENDED. */ private int status = STATUS_IDLE; /** * The reason for a connection closing. */ private String closeReason; /** * 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. *

*/ private boolean longRunning; /** * Flag to indicate that this connection had errors and should be checked to * make sure it is okay. */ private boolean hadErrors; private boolean resetAutoCommit; /** * The last start time. When the connection was given to a thread. */ private long startUseTime; /** * The last end time of this connection. This is to calculate the usage * time. */ private long lastUseTime; private long exeStartNanos; /** * The last statement executed by this connection. */ private String lastStatement; /** * The non avaje method that created the connection. */ private String createdByMethod; /** * Used to find connection pool leaks. */ private StackTraceElement[] stackTrace; private final int maxStackTrace; /** * Slot position in the BusyConnectionBuffer. */ private int slotId; private boolean resetIsolationReadOnlyRequired; /** * 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(ConnectionPool pool, int uniqueId, Connection connection) { super(connection); this.pool = pool; this.connection = connection; this.name = pool.getName() + "" + uniqueId; this.pstmtCache = new PstmtCache(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. */ int getSlotId() { return slotId; } /** * Set the slot position in the busy buffer. */ void setSlotId(int slotId) { this.slotId = slotId; } /** * Return a string to identify the connection. */ String getName() { return name; } private String getNameSlot() { return name + ":" + slotId; } public String toString() { return getDescription(); } private long getBusySeconds() { return (System.currentTimeMillis() - startUseTime) / 1000; } String getDescription() { return "name[" + name + "] slot[" + slotId + "] startTime[" + getStartUseTime() + "] busySeconds[" + getBusySeconds() + "] createdBy[" + getCreatedByMethod() + "] stmt[" + getLastStatement() + "]"; } String getFullDescription() { return "name[" + name + "] slot[" + slotId + "] startTime[" + getStartUseTime() + "] busySeconds[" + getBusySeconds() + "] stackTrace[" + getStackTraceAsString() + "] stmt[" + getLastStatement() + "]"; } PooledConnectionStatistics getStatistics() { return stats; } /** * Return true if the connection should be treated as long running (skip connection pool leak check). */ 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 */ void closeConnectionFully(boolean logErrors) { if (pool != null) { // allow collection of load statistics pool.reportClosingConnection(this); } if (logger.isDebugEnabled()) { logger.debug("Closing Connection[{}] slot[{}] reason[{}] stats: {} , pstmtStats: {} ", name, slotId, closeReason, stats.getValues(false), pstmtCache.getDescription()); } try { if (connection.isClosed()) { // Typically the JDBC Driver has its own JVM shutdown hook and already // closed the connections in our DataSource pool so making this DEBUG level logger.debug("Closing Connection[{}] that is already closed?", name); return; } } catch (SQLException ex) { if (logErrors) { logger.error("Error checking if connection [" + getNameSlot() + "] is closed", ex); } } try { for (ExtendedPreparedStatement ps : pstmtCache.values()) { ps.closeDestroy(); } } catch (SQLException ex) { if (logErrors) { logger.warn("Error when closing connection Statements", ex); } } try { connection.close(); } catch (SQLException ex) { if (logErrors || logger.isDebugEnabled()) { logger.error("Error when fully closing connection [" + getFullDescription() + "]", ex); } } } /** * 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) { markWithError(); 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) { markWithError(); throw ex; } } /** * Return a PreparedStatement back into the cache. */ void returnPreparedStatement(ExtendedPreparedStatement pstmt) { synchronized (pstmtMonitor) { if (!pstmtCache.returnStatement(pstmt)) { try { // Already an entry in the cache with the exact same SQL... pstmt.closeDestroy(); } catch (SQLException e) { logger.error("Error closing Pstmt", e); } } } } /** * This will try to use a cache of PreparedStatements. */ public PreparedStatement prepareStatement(String sql, int returnKeysFlag) throws SQLException { StringBuilder cacheKey = new StringBuilder(sql.length() + 50); cacheKey.append(sql); cacheKey.append(':').append(currentSchema); cacheKey.append(':').append(returnKeysFlag); return prepareStatement(sql, true, returnKeysFlag, cacheKey.toString()); } /** * This will try to use a cache of PreparedStatements. */ public PreparedStatement prepareStatement(String sql) throws SQLException { StringBuilder cacheKey = new StringBuilder(sql.length() + 50); cacheKey.append(sql); cacheKey.append(':').append(currentSchema); return prepareStatement(sql, false, 0, cacheKey.toString()); } /** * 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) { throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"); } try { synchronized (pstmtMonitor) { lastStatement = sql; // try to get a matching cached PStmt from the cache. ExtendedPreparedStatement pstmt = pstmtCache.remove(cacheKey); if (pstmt != null) { return pstmt.reset(); } // create a new PreparedStatement PreparedStatement actualPstmt; if (useFlag) { actualPstmt = connection.prepareStatement(sql, flag); } else { actualPstmt = connection.prepareStatement(sql); } return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey); } } catch (SQLException ex) { markWithError(); 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 lastStatement = sql; return connection.prepareStatement(sql, resultSetType, resultSetConcurreny); } catch (SQLException ex) { markWithError(); throw ex; } } /** * Reset the connection for returning to the client. Resets the status, * startUseTime and hadErrors. */ void resetForUse() { this.status = STATUS_ACTIVE; this.startUseTime = System.currentTimeMillis(); this.exeStartNanos = System.nanoTime(); 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. *

*/ void markWithError() { hadErrors = true; } /** * 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()"); } long durationNanos = System.nanoTime() - exeStartNanos; stats.add(durationNanos, hadErrors); if (hadErrors) { if (!pool.validateConnection(this)) { // the connection is BAD, remove it, close it and test the pool pool.returnConnectionForceClose(this); return; } } try { // reset the autoCommit back if client code changed it if (resetAutoCommit) { connection.setAutoCommit(pool.isAutoCommit()); resetAutoCommit = false; } // 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, remove it, close it and test the pool logger.warn("Error when trying to return connection to pool, closing fully.", ex); pool.returnConnectionForceClose(this); } } private void resetIsolationReadOnly() throws SQLException { // reset the transaction isolation if the client code changed it //noinspection MagicConstant if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) { //noinspection MagicConstant 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? logger.warn("Closing Connection on finalize() - {}", getFullDescription()); closeConnectionFully(false); } } catch (Exception e) { logger.error("Error when finalize is closing a connection? (unexpected)", e); } super.finalize(); } /** * Return true if the connection is too old. */ private boolean exceedsMaxAge(long maxAgeMillis) { if (maxAgeMillis > 0 && (creationTime < (System.currentTimeMillis() - maxAgeMillis))) { this.closeReason = REASON_MAXAGE; return true; } return false; } boolean shouldTrimOnReturn(long lastResetTime, long maxAgeMillis) { if (creationTime <= lastResetTime) { this.closeReason = REASON_RESET; return true; } return exceedsMaxAge(maxAgeMillis); } /** * Return true if the connection has been idle for too long or is too old. */ boolean shouldTrim(long usedSince, long createdSince) { if (lastUseTime < usedSince) { // been idle for too long so trim it this.closeReason = REASON_IDLE; return true; } if (createdSince > 0 && createdSince > creationTime) { // exceeds max age so trim it this.closeReason = REASON_MAXAGE; return true; } return false; } /** * Return the time the connection was passed to the client code. *

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

*/ private 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. *

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

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

*/ void setLastStatement(String lastStatement) { this.lastStatement = lastStatement; if (logger.isTraceEnabled()) { logger.trace(".setLastStatement[" + lastStatement + "]"); } } /** * 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) { markWithError(); 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) { markWithError(); 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) { markWithError(); 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); resetAutoCommit = true; } catch (SQLException ex) { markWithError(); 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) { markWithError(); throw ex; } } public Savepoint setSavepoint(String savepointName) throws SQLException { try { return connection.setSavepoint(savepointName); } catch (SQLException ex) { markWithError(); throw ex; } } public void rollback(Savepoint sp) throws SQLException { try { connection.rollback(sp); } catch (SQLException ex) { markWithError(); throw ex; } } public void releaseSavepoint(Savepoint sp) throws SQLException { try { connection.releaseSavepoint(sp); } catch (SQLException ex) { markWithError(); throw ex; } } public void setHoldability(int i) throws SQLException { try { connection.setHoldability(i); } catch (SQLException ex) { markWithError(); throw ex; } } public int getHoldability() throws SQLException { try { return connection.getHoldability(); } catch (SQLException ex) { markWithError(); throw ex; } } public Statement createStatement(int i, int x, int y) throws SQLException { try { return connection.createStatement(i, x, y); } catch (SQLException ex) { markWithError(); 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) { markWithError(); throw ex; } } public PreparedStatement prepareStatement(String s, int[] i) throws SQLException { try { return connection.prepareStatement(s, i); } catch (SQLException ex) { markWithError(); throw ex; } } public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException { try { return connection.prepareStatement(s, s2); } catch (SQLException ex) { markWithError(); 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) { markWithError(); throw ex; } } /** * Returns the method that created the connection. *

* Used to help finding connection pool leaks. *

*/ private 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)) { 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("org.avaje.datasource.")) { return true; } return methodLine.startsWith("com.avaje.ebean"); } /** * Set the stack trace to help find connection pool leaks. */ void setStackTrace(StackTraceElement[] stackTrace) { this.stackTrace = stackTrace; } /** * Return the stackTrace as a String for logging purposes. */ private String getStackTraceAsString() { StackTraceElement[] stackTrace = getStackTrace(); if (stackTrace == null) { return ""; } return Arrays.toString(stackTrace); } /** * Return the full stack trace that got the connection from the pool. You * could use this if getCreatedByMethod() doesn't work for you. */ private 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 - 2025 Weber Informatics LLC | Privacy Policy