/*
 * XAPool: Open Source XA JDBC Pool
 * Copyright (C) 2003 Objectweb.org
 * Initial Developer: Lutris Technologies Inc.
 * Contact: xapool-public@lists.debian-sf.objectweb.org
 *
 * 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 */
package org.enhydra.jdbc.standard;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.sql.Connection;
import org.enhydra.jdbc.core.CoreConnection;
import org.enhydra.jdbc.util.LRUCache;

This is an implementation of java.sql.Connection which simply delegates almost everything to an underlying physical implemention of the same interface. It relies on a StandardPooledConnection to create it and to supply the physical connection and a cache of PreparedStatements. This class will try to re-use PreparedStatements wherever possible and will add to the cache when totally new PreparedStatements get created.
/** * This is an implementation of java.sql.Connection which simply * delegates almost everything to an underlying physical implemention * of the same interface. * * It relies on a StandardPooledConnection to create it and to supply the * physical connection and a cache of PreparedStatements. This class will * try to re-use PreparedStatements wherever possible and will add to the * cache when totally new PreparedStatements get created. */
public class StandardConnectionHandle extends CoreConnection { StandardPooledConnection pooledCon; // the pooled connection that created this object protected Hashtable masterPrepStmtCache; // the hashtable of caches, indexed by physical connection int preparedStmtCacheSize; // the size of the connection-specific cache protected LRUCache preparedStatementCache = null; // prepared statements indexed by SQL string public Hashtable inUse; // prepared statements that are currently in use private boolean closed; // set true when this connection has been closed public boolean isReallyUsed = false;
Constructor.
/** * Constructor. */
public StandardConnectionHandle( StandardPooledConnection pooledCon, Hashtable preparedStatementCache, int preparedStmtCacheSize) { super(pooledCon.getPhysicalConnection()); // get the real connection this.pooledCon = pooledCon; // first save parameters masterPrepStmtCache = preparedStatementCache; this.preparedStmtCacheSize = preparedStmtCacheSize; log = pooledCon.dataSource.log; setupPreparedStatementCache(); inUse = new Hashtable(10, 0.5f); log.debug( "StandardConnectionHandle:new StandardConnectionHandle with " + preparedStmtCacheSize + " prepared statement"); } protected void setupPreparedStatementCache() { log.debug("StandardConnectionHandle:setupPreparedStatementCache start"); if (preparedStmtCacheSize == 0) { log.debug( "StandardConnectionHandle:setupPreparedStatementCache return with 0"); preparedStatementCache = null; return; } if (con == null) log.warn("Connection is null"); else { preparedStatementCache = (LRUCache) masterPrepStmtCache.get(con.toString()); if (preparedStatementCache == null) { preparedStatementCache = new PreparedStatementCache(preparedStmtCacheSize); preparedStatementCache.setLogger(log); masterPrepStmtCache.put(con.toString(), preparedStatementCache); log.debug( "StandardConnectionHandle:setupPreparedStatementCache " + "preparedStatementCache.size(lru)='" + preparedStatementCache.LRUSize() + "' " + "preparedStatementCache.size(cache)='" + preparedStatementCache.cacheSize() + "' " + "masterPrepStmtCache.size='" + masterPrepStmtCache.size() + "' "); } else preparedStatementCache.setLogger(log); } log.debug("StandardConnectionHandle:setupPreparedStatementCache end"); }
Pre-invokation of the delegation, in case of connection is closed, we throw an exception
/** * Pre-invokation of the delegation, in case of connection is * closed, we throw an exception */
public void preInvoke() throws SQLException { if (closed) throw new SQLException("Connection is closed"); }
Exception management : catch or throw the exception
/** * Exception management : catch or throw the exception */
public void catchInvoke(SQLException e) throws SQLException { //ConnectionEvent event = new ConnectionEvent (pooledCon);// create event associate with the connection //pooledCon.connectionErrorOccurred(event); // ppoled have to be closed throw (e); // throw the exception }
Closes this StandardConnectionHandle and prevents it from being reused. It also returns used PreparedStatements to the PreparedStatement cache and notifies all listeners.
/** * Closes this StandardConnectionHandle and prevents it * from being reused. It also returns used PreparedStatements * to the PreparedStatement cache and notifies all listeners. */
synchronized public void close() throws SQLException { log.debug("StandardConnectionHandle:close"); // Note - we don't check to see if already closed. Some servers get confused. closed = true; // connection now closed Enumeration keys = inUse.keys(); // get any prepared statements in use while (keys.hasMoreElements()) { // while more prepared statements used Object key = keys.nextElement(); // get next key returnToCache(key); // return prepared statement to cache } pooledCon.closeEvent(); // notify listeners if (preparedStatementCache != null) preparedStatementCache.cleanupAll(); if ((preparedStatementCache != null) && (masterPrepStmtCache != null) && (log != null)) log.debug( "StandardConnectionHandle:close " + "preparedStatementCache.size(lru)='" + preparedStatementCache.LRUSize() + "' " + "preparedStatementCache.size(cache)='" + preparedStatementCache.cacheSize() + "' " + "masterPrepStmtCache.size='" + masterPrepStmtCache.size() + "' "); }
Removes a prepared statement from the inUse list and returns it to the cache.
/** * Removes a prepared statement from the inUse list * and returns it to the cache. */
void returnToCache(Object key, Connection theCon) { Object value = inUse.remove(key); // remove key/value from used statements if (value != null) { LRUCache theCache = (LRUCache) masterPrepStmtCache.get(theCon.toString()); theCache.put(key, value); // place back in cache, ready for re-use } } void returnToCache(Object key) { returnToCache(key, con); }
Checks to see if a prepared statement with the same concurrency has already been created. If not, then a new prepared statement is created and added to the cache. If a prepared statement is found in the cache then it is removed from the cache and placed on the "inUse" list. This ensures that if multiple threads use the same StandardConnectionHandle, or a single thread does multiple prepares using the same SQL, then DIFFERENT prepared statements will be returned.
/** * Checks to see if a prepared statement with the same concurrency * has already been created. If not, then a new prepared statement * is created and added to the cache. * * If a prepared statement is found in the cache then it is removed * from the cache and placed on the "inUse" list. This ensures that * if multiple threads use the same StandardConnectionHandle, or a single * thread does multiple prepares using the same SQL, then DIFFERENT * prepared statements will be returned. */
synchronized PreparedStatement checkPreparedCache( String sql, int type, int concurrency, int holdability) throws SQLException { log.debug( "StandardConnectionHandle:checkPreparedCache sql='" + sql + "'"); PreparedStatement ret = null; // the return value // NOTE - We include the Connection in the lookup key. This has no // effect here but is needed by StandardXAConnection where the the physical // Connection used can vary over time depending on the global transaction. String lookupKey = sql + type + concurrency; // used to lookup statements if (preparedStatementCache != null) { Object obj = preparedStatementCache.get(lookupKey); // see if there's a PreparedStatement already if (obj != null) { // if there is ret = (PreparedStatement) obj; // use as return value try { ret.clearParameters(); // make it look like new } catch (SQLException e) { // Bad statement, so we have to create a new one ret = createPreparedStatement(sql, type, concurrency, holdability); } preparedStatementCache.remove(lookupKey); // make sure it cannot be re-used inUse.put(lookupKey, ret); // make sure it gets reused by later delegates } else { // no PreparedStatement ready ret = createPreparedStatement(sql, type, concurrency, holdability); inUse.put(lookupKey, ret); // will get saved in prepared statement cache } } else { ret = createPreparedStatement(sql, type, concurrency, holdability); } // We don't actually give the application a real PreparedStatement. Instead // they get a StandardPreparedStatement that delegates everything except // PreparedStatement.close(); ret = new StandardPreparedStatement(this, ret, lookupKey); return ret; } synchronized PreparedStatement checkPreparedCache( String sql, int autogeneratedkeys) throws SQLException { log.debug( "StandardConnectionHandle:checkPreparedCache sql='" + sql + "'"); PreparedStatement ret = null; // the return value // NOTE - We include the Connection in the lookup key. This has no // effect here but is needed by StandardXAConnection where the the physical // Connection used can vary over time depending on the global transaction. String lookupKey = sql + autogeneratedkeys; // used to lookup statements if (preparedStatementCache != null) { Object obj = preparedStatementCache.get(lookupKey); // see if there's a PreparedStatement already if (obj != null) { // if there is ret = (PreparedStatement) obj; // use as return value try { ret.clearParameters(); // make it look like new } catch (SQLException e) { // Bad statement, so we have to create a new one ret = createPreparedStatement(sql, autogeneratedkeys); } preparedStatementCache.remove(lookupKey); // make sure it cannot be re-used inUse.put(lookupKey, ret); // make sure it gets reused by later delegates } else { // no PreparedStatement ready ret = createPreparedStatement(sql, autogeneratedkeys); inUse.put(lookupKey, ret); // will get saved in prepared statement cache } } else { ret = createPreparedStatement(sql, autogeneratedkeys); } // We don't actually give the application a real PreparedStatement. Instead // they get a StandardPreparedStatement that delegates everything except // PreparedStatement.close(); ret = new StandardPreparedStatement(this, ret, lookupKey); return ret; } protected PreparedStatement createPreparedStatement( String sql, int type, int concurrency, int holdability) throws SQLException { log.debug( "StandardConnectionHandle:createPreparedStatement type ='" + type + "'"); if (type == 0 && holdability == 0) { // if no type or concurrency specified return con.prepareStatement(sql); // create new prepared statement } else if (holdability == 0) { return con.prepareStatement(sql, type, concurrency); // create new prepared statement } else return con.prepareStatement(sql, type, concurrency, holdability); } protected PreparedStatement createPreparedStatement( String sql, int autogeneratedkeys) throws SQLException { log.debug( "StandardConnectionHandle:createPreparedStatement autogeneratedkeys ='" + autogeneratedkeys + "'"); return con.prepareStatement(sql, autogeneratedkeys); // create new prepared statement }
Creates a PreparedStatement for the given SQL. If possible, the statement is fetched from the cache.
/** * Creates a PreparedStatement for the given SQL. If possible, the * statement is fetched from the cache. */
public PreparedStatement prepareStatement(String sql) throws SQLException { log.debug( "StandardConnectionHandle:prepareStatement sql='" + sql + "'"); preInvoke(); try { return checkPreparedCache(sql, 0, 0, 0); } catch (SQLException e) { catchInvoke(e); } return null; }
Creates a PreparedStatement for the given SQL, type and concurrency. If possible, the statement is fetched from the cache.
/** * Creates a PreparedStatement for the given SQL, type and concurrency. * If possible, the statement is fetched from the cache. */
public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency) throws SQLException { preInvoke(); try { return checkPreparedCache(sql, resultSetType, resultSetConcurrency, 0); } catch (SQLException e) { catchInvoke(e); } return null; } public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { preInvoke(); try { return checkPreparedCache(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } catch (SQLException e) { catchInvoke(e); } return null; } public boolean isClosed() throws SQLException { return closed; } public CallableStatement prepareCall( String sql, int resultSetType, int resultSetConcurrency) throws SQLException { preInvoke(); try { return con.prepareCall(sql, resultSetType, resultSetConcurrency); } catch (SQLException e) { catchInvoke(e); } return null; } }