Author: psteitz
Date: Sat Nov 21 20:04:53 2009
New Revision: 882981
URL: http://svn.apache.org/viewvc?rev=882981&view=rev
Log:
Made PoolingConnection pool CallableStatements.
JIRA: DBCP-204
Reported and patched by Wei Chen.
Added:
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolableCallableStatement.java
(with props)
Modified:
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/BasicDataSource.java
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolingConnection.java
commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TestPStmtPooling.java
commons/proper/dbcp/trunk/xdocs/changes.xml
Modified:
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/BasicDataSource.java
URL:
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/BasicDataSource.java?rev=882981&r1=882980&r2=882981&view=diff
==============================================================================
---
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/BasicDataSource.java
(original)
+++
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/BasicDataSource.java
Sat Nov 21 20:04:53 2009
@@ -427,14 +427,15 @@
}
/**
- * Prepared statement pooling for this pool.
+ * Prepared statement pooling for this pool. When this property is set to
<code>true</code>
+ * both PreparedStatements and CallableStatements are pooled.
*/
protected boolean poolPreparedStatements = false;
/**
* Returns true if we are pooling statements.
*
- * @return true if prepared statements are pooled
+ * @return true if prepared and callable statements are pooled
*/
public synchronized boolean isPoolPreparedStatements() {
return this.poolPreparedStatements;
@@ -456,10 +457,15 @@
}
/**
- * The maximum number of open statements that can be allocated from
+ * <p>The maximum number of open statements that can be allocated from
* the statement pool at the same time, or non-positive for no limit.
Since
* a connection usually only uses one or two statements at a time, this is
- * mostly used to help detect resource leaks.
+ * mostly used to help detect resource leaks.</p>
+ *
+ * <p>Note: As of version 1.3, CallableStatements (those produced by
{...@link Connection#prepareCall})
+ * are pooled along with PreparedStatements (produced by {...@link
Connection#prepareStatement})
+ * and <code>maxOpenPreparedStatements</code> limits the total number of
prepared or callable statements
+ * that may be in use at a given time.</p>
*/
protected int maxOpenPreparedStatements =
GenericKeyedObjectPool.DEFAULT_MAX_TOTAL;
Added:
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolableCallableStatement.java
URL:
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolableCallableStatement.java?rev=882981&view=auto
==============================================================================
---
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolableCallableStatement.java
(added)
+++
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolableCallableStatement.java
Sat Nov 21 20:04:53 2009
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.dbcp;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.apache.commons.pool.KeyedObjectPool;
+
+/**
+ * A {...@link DelegatingCallableStatement} that cooperates with
+ * {...@link PoolingConnection} to implement a pool of {...@link
CallableStatement}s.
+ * <p>
+ * The {...@link #close} method returns this statement to its containing pool.
(See {...@link PoolingConnection}.)
+ *
+ * @see PoolingConnection
+ * @version $Revision$ $Date$
+ * @since 1.3
+ */
+public class PoolableCallableStatement extends DelegatingCallableStatement
implements CallableStatement {
+
+ /**
+ * The {...@link KeyedObjectPool} from which this CallableStatement was
obtained.
+ */
+ protected KeyedObjectPool _pool = null;
+
+ /**
+ * Key for this statement in the containing {...@link KeyedObjectPool}.
+ */
+ protected Object _key = null;
+
+ public PoolableCallableStatement(DelegatingConnection c,
CallableStatement s) {
+ super(c, s);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param stmt the underlying {...@link CallableStatement}
+ * @param key the key for this statement in the {...@link
KeyedObjectPool}
+ * @param pool the {...@link KeyedObjectPool} from which this
CallableStatement was obtained
+ * @param conn the {...@link Connection} that created this
CallableStatement
+ */
+ public PoolableCallableStatement(CallableStatement stmt, Object key,
KeyedObjectPool pool, Connection conn) {
+ super((DelegatingConnection)conn, stmt);
+ _pool = pool;
+ _key = key;
+
+ // Remove from trace now because this statement will be
+ // added by the activate method.
+ if(_conn != null) {
+ _conn.removeTrace(this);
+ }
+ }
+
+ /**
+ * Returns the CallableStatement to the pool. If {...@link #isClosed()},
this is a No-op.
+ */
+ public void close() throws SQLException {
+ // calling close twice should have no effect
+ if (!isClosed()) {
+ try {
+ _pool.returnObject(_key,this);
+ } catch(SQLException e) {
+ throw e;
+ } catch(RuntimeException e) {
+ throw e;
+ } catch(Exception e) {
+ throw new SQLNestedException("Cannot close CallableStatement
(return to pool failed)", e);
+ }
+ }
+ }
+
+ /**
+ * Activates after retrieval from the pool. Adds a trace for this
CallableStatement to the Connection
+ * that created it.
+ */
+ protected void activate() throws SQLException {
+ _closed = false;
+ if( _conn != null ) {
+ _conn.addTrace( this );
+ }
+ super.activate();
+ }
+
+ /**
+ * Passivates to prepare for return to the pool. Removes the trace
associated with this CallableStatement
+ * from the Connection that created it. Also closes any associated
ResultSets.
+ */
+ protected void passivate() throws SQLException {
+ _closed = true;
+ if( _conn != null ) {
+ _conn.removeTrace(this);
+ }
+
+ // The JDBC spec requires that a statment close any open
+ // ResultSet's when it is closed.
+ // FIXME The PreparedStatement we're wrapping should handle
this for us.
+ // See DBCP-10 for what could happen when ResultSets are closed
twice.
+ List resultSets = getTrace();
+ if(resultSets != null) {
+ ResultSet[] set = (ResultSet[])resultSets.toArray(new
ResultSet[resultSets.size()]);
+ for(int i = 0; i < set.length; i++) {
+ set[i].close();
+ }
+ clearTrace();
+ }
+
+ super.passivate();
+ }
+
+}
Propchange:
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolableCallableStatement.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolableCallableStatement.java
------------------------------------------------------------------------------
svn:keywords = Date Revision
Propchange:
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolableCallableStatement.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified:
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolingConnection.java
URL:
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolingConnection.java?rev=882981&r1=882980&r2=882981&view=diff
==============================================================================
---
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolingConnection.java
(original)
+++
commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/PoolingConnection.java
Sat Nov 21 20:04:53 2009
@@ -17,6 +17,7 @@
package org.apache.commons.dbcp;
+import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@@ -29,10 +30,12 @@
/**
* A {...@link DelegatingConnection} that pools {...@link PreparedStatement}s.
* <p>
- * My {...@link #prepareStatement} methods, rather than creating a new
{...@link PreparedStatement}
- * each time, may actually pull the {...@link PreparedStatement} from a pool
of unused statements.
- * The {...@link PreparedStatement#close} method of the returned {...@link
PreparedStatement} doesn't
- * actually close the statement, but rather returns it to my pool. (See
{...@link PoolablePreparedStatement}.)
+ * The {...@link #prepareStatement} and {...@link #prepareCall} methods,
rather than creating a new PreparedStatement
+ * each time, may actually pull the statement from a pool of unused statements.
+ * The {...@link PreparedStatement#close} method of the returned statement
doesn't
+ * actually close the statement, but rather returns it to the pool.
+ * (See {...@link PoolablePreparedStatement}, {...@link
PoolableCallableStatement}.)
+ *
*
* @see PoolablePreparedStatement
* @author Rodney Waldhoff
@@ -40,9 +43,16 @@
* @version $Revision$ $Date$
*/
public class PoolingConnection extends DelegatingConnection implements
Connection, KeyedPoolableObjectFactory {
- /** My pool of {...@link PreparedStatement}s. */
+ /** Pool of {...@link PreparedStatement}s. and {...@link
CallableStatement}s */
protected KeyedObjectPool _pstmtPool = null;
+ /** Prepared Statement type */
+ private static final byte STATEMENT_PREPAREDSTMT = 0;
+
+ /** Callable Statement type */
+ private static final byte STATEMENT_CALLABLESTMT = 1;
+
+
/**
* Constructor.
* @param c the underlying {...@link Connection}.
@@ -54,7 +64,7 @@
/**
* Constructor.
* @param c the underlying {...@link Connection}.
- * @param pool {...@link KeyedObjectPool} of {...@link PreparedStatement}s
+ * @param pool {...@link KeyedObjectPool} of {...@link PreparedStatement}s
and {...@link CallableStatement}s.
*/
public PoolingConnection(Connection c, KeyedObjectPool pool) {
super(c);
@@ -63,7 +73,7 @@
/**
- * Close and free all {...@link PreparedStatement}s from my pool, and
+ * Close and free all {...@link PreparedStatement}s or {...@link
CallableStatement} from my pool, and
* close my underlying connection.
*/
public synchronized void close() throws SQLException {
@@ -122,6 +132,48 @@
throw (SQLException) new SQLException("Borrow prepareStatement
from pool failed").initCause(e);
}
}
+
+ /**
+ * Create or obtain a {...@link CallableStatement} from the pool.
+ *
+ * @param sql the sql string used to define the CallableStatement
+ * @return a {...@link PoolableCallableStatement}
+ * @throws SQLException
+ * @since 1.3
+ */
+ public CallableStatement prepareCall(String sql) throws SQLException {
+ try {
+ return (CallableStatement) (_pstmtPool.borrowObject(createKey(sql,
STATEMENT_CALLABLESTMT)));
+ } catch (NoSuchElementException e) {
+ throw new SQLNestedException("MaxOpenCallableStatements limit
reached", e);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new SQLNestedException("Borrow callableStatement from pool
failed", e);
+ }
+ }
+
+ /**
+ * Create or obtain a {...@link CallableStatement} from the pool.
+ *
+ * @param sql the sql string used to define the CallableStatement
+ * @return a {...@link PoolableCallableStatement}
+ * @throws SQLException
+ * @since 1.3
+ */
+ public CallableStatement prepareCall(String sql, int resultSetType, int
resultSetConcurrency) throws SQLException {
+ try {
+ return (CallableStatement) (_pstmtPool.borrowObject(createKey(sql,
resultSetType,
+ resultSetConcurrency, STATEMENT_CALLABLESTMT)));
+ } catch (NoSuchElementException e) {
+ throw new SQLNestedException("MaxOpenCallableStatements limit
reached", e);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new SQLNestedException("Borrow callableStatement from pool
failed", e);
+ }
+ }
+
// ------------------- JDBC 3.0 -----------------------------------------
// Will be commented by the build process on a JDBC 2.0 system
@@ -166,6 +218,17 @@
} catch (SQLException e) {}
return new PStmtKey(normalizeSQL(sql), catalog, resultSetType,
resultSetConcurrency);
}
+
+ /**
+ * Create a PStmtKey for the given arguments.
+ */
+ protected Object createKey(String sql, int resultSetType, int
resultSetConcurrency, byte stmtType) {
+ String catalog = null;
+ try {
+ catalog = getCatalog();
+ } catch (Exception e) {}
+ return new PStmtKey(normalizeSQL(sql), catalog, resultSetType,
resultSetConcurrency, stmtType);
+ }
/**
* Create a PStmtKey for the given arguments.
@@ -177,6 +240,17 @@
} catch (SQLException e) {}
return new PStmtKey(normalizeSQL(sql), catalog);
}
+
+ /**
+ * Create a PStmtKey for the given arguments.
+ */
+ protected Object createKey(String sql, byte stmtType) {
+ String catalog = null;
+ try {
+ catalog = getCatalog();
+ } catch (Exception e) {}
+ return new PStmtKey(normalizeSQL(sql), catalog, stmtType);
+ }
/**
* Normalize the given SQL statement, producing a
@@ -187,36 +261,42 @@
}
/**
- * My {...@link KeyedPoolableObjectFactory} method for creating
- * {...@link PreparedStatement}s.
+ * {...@link KeyedPoolableObjectFactory} method for creating
+ * {...@link PoolablePreparedStatement}s or {...@link
PoolableCallableStatements}.
+ * The {...@link PStmtKey#_stmtType} field in the key determines whether
+ * a PoolablePreparedStatement or PoolableCallableStatement is created.
+ *
* @param obj the key for the {...@link PreparedStatement} to be created
*/
public Object makeObject(Object obj) throws Exception {
if(null == obj || !(obj instanceof PStmtKey)) {
throw new IllegalArgumentException("Prepared statement key is null
or invalid.");
} else {
- // _openPstmts++;
PStmtKey key = (PStmtKey)obj;
- if(null == key._resultSetType && null ==
key._resultSetConcurrency) {
- return new
PoolablePreparedStatement(getDelegate().prepareStatement(key._sql),key,_pstmtPool,this);
- } else {
- if (null == key._resultSetType || null ==
key._resultSetConcurrency) {
- throw new IllegalArgumentException("Invalid prepared
statement key.");
- }
- return new
PoolablePreparedStatement(getDelegate().prepareStatement(
-
key._sql,key._resultSetType.intValue(),key._resultSetConcurrency.intValue()),key,_pstmtPool,this);
+ if( null == key._resultSetType && null ==
key._resultSetConcurrency ) {
+ return key._stmtType == STATEMENT_PREPAREDSTMT ?
+ new
PoolablePreparedStatement(getDelegate().prepareStatement( key._sql), key,
_pstmtPool, this) :
+ new PoolableCallableStatement(getDelegate().prepareCall(
key._sql ), key, _pstmtPool, this);
+
+ }else {
+ return key._stmtType == STATEMENT_PREPAREDSTMT ?
+ new
PoolablePreparedStatement(getDelegate().prepareStatement(
+ key._sql,
key._resultSetType.intValue(),key._resultSetConcurrency.intValue()), key,
_pstmtPool, this) :
+ new PoolableCallableStatement( getDelegate().prepareCall(
+ key._sql,key._resultSetType.intValue(),
key._resultSetConcurrency.intValue() ), key, _pstmtPool, this);
}
}
}
/**
- * My {...@link KeyedPoolableObjectFactory} method for destroying
- * {...@link PreparedStatement}s.
+ * {...@link KeyedPoolableObjectFactory} method for destroying
+ * PoolablePreparedStatements and PoolableCallableStatements.
+ * Closes the underlying statement.
+ *
* @param key ignored
- * @param obj the {...@link PreparedStatement} to be destroyed.
+ * @param obj the pooled statement to be destroyed.
*/
public void destroyObject(Object key, Object obj) throws Exception {
- //_openPstmts--;
if(obj instanceof DelegatingPreparedStatement) {
((DelegatingPreparedStatement)obj).getInnermostDelegate().close();
} else {
@@ -225,8 +305,9 @@
}
/**
- * My {...@link KeyedPoolableObjectFactory} method for validating
- * {...@link PreparedStatement}s.
+ * {...@link KeyedPoolableObjectFactory} method for validating
+ * pooled statements. Currently always returns true.
+ *
* @param key ignored
* @param obj ignored
* @return <tt>true</tt>
@@ -236,18 +317,21 @@
}
/**
- * My {...@link KeyedPoolableObjectFactory} method for activating
- * {...@link PreparedStatement}s. (Currently a no-op.)
+ * {...@link KeyedPoolableObjectFactory} method for activating
+ * pooled statements.
+ *
* @param key ignored
- * @param obj ignored
+ * @param obj pooled statement to be activated
*/
public void activateObject(Object key, Object obj) throws Exception {
((DelegatingPreparedStatement)obj).activate();
}
/**
- * My {...@link KeyedPoolableObjectFactory} method for passivating
- * {...@link PreparedStatement}s. Currently invokes {...@link
PreparedStatement#clearParameters}.
+ * {...@link KeyedPoolableObjectFactory} method for passivating
+ * {...@link PreparedStatement}s or {...@link CallableStatement}s.
+ * Invokes {...@link PreparedStatement#clearParameters}.
+ *
* @param key ignored
* @param obj a {...@link PreparedStatement}
*/
@@ -268,11 +352,25 @@
* A key uniquely identifiying {...@link PreparedStatement}s.
*/
static class PStmtKey {
+
+ /** SQL defining Prepared or Callable Statement */
protected String _sql = null;
+
+ /** Result set type */
protected Integer _resultSetType = null;
+
+ /** Result set concurrency */
protected Integer _resultSetConcurrency = null;
+
+ /** Database catalog */
protected String _catalog = null;
+ /**
+ * Statement type. Either STATEMENT_PREPAREDSTMT (PreparedStatement)
+ * or STATEMENT_CALLABLESTMT (CallableStatement)
+ */
+ protected byte _stmtType = STATEMENT_PREPAREDSTMT;
+
PStmtKey(String sql) {
_sql = sql;
}
@@ -281,6 +379,12 @@
_sql = sql;
_catalog = catalog;
}
+
+ PStmtKey(String sql, String catalog, byte stmtType) {
+ _sql = sql;
+ _catalog = catalog;
+ _stmtType = stmtType;
+ }
PStmtKey(String sql, int resultSetType, int resultSetConcurrency) {
_sql = sql;
@@ -294,6 +398,14 @@
_resultSetType = new Integer(resultSetType);
_resultSetConcurrency = new Integer(resultSetConcurrency);
}
+
+ PStmtKey(String sql, String catalog, int resultSetType, int
resultSetConcurrency, byte stmtType) {
+ _sql = sql;
+ _catalog = catalog;
+ _resultSetType = new Integer(resultSetType);
+ _resultSetConcurrency = new Integer(resultSetConcurrency);
+ _stmtType = stmtType;
+ }
public boolean equals(Object that) {
try {
@@ -301,7 +413,8 @@
return( ((null == _sql && null == key._sql) ||
_sql.equals(key._sql)) &&
((null == _catalog && null == key._catalog) ||
_catalog.equals(key._catalog)) &&
((null == _resultSetType && null ==
key._resultSetType) || _resultSetType.equals(key._resultSetType)) &&
- ((null == _resultSetConcurrency && null ==
key._resultSetConcurrency) ||
_resultSetConcurrency.equals(key._resultSetConcurrency))
+ ((null == _resultSetConcurrency && null ==
key._resultSetConcurrency) ||
_resultSetConcurrency.equals(key._resultSetConcurrency)) &&
+ (_stmtType == key._stmtType)
);
} catch(ClassCastException e) {
return false;
@@ -327,6 +440,8 @@
buf.append(_resultSetType);
buf.append(", resultSetConcurrency=");
buf.append(_resultSetConcurrency);
+ buf.append(", statmentType=");
+ buf.append(_stmtType);
return buf.toString();
}
}
Modified:
commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TestPStmtPooling.java
URL:
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TestPStmtPooling.java?rev=882981&r1=882980&r2=882981&view=diff
==============================================================================
---
commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TestPStmtPooling.java
(original)
+++
commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TestPStmtPooling.java
Sat Nov 21 20:04:53 2009
@@ -70,6 +70,42 @@
assertSame(ustmt1, ustmt2);
}
+ public void testCallableStatementPooling() throws Exception {
+ new TesterDriver();
+ ConnectionFactory connFactory = new DriverManagerConnectionFactory(
+ "jdbc:apache:commons:testdriver","u1","p1");
+
+ ObjectPool connPool = new GenericObjectPool();
+ KeyedObjectPoolFactory stmtPoolFactory = new
GenericKeyedObjectPoolFactory(null);
+
+ new PoolableConnectionFactory(connFactory, connPool, stmtPoolFactory,
+ null, false, true);
+
+ DataSource ds = new PoolingDataSource(connPool);
+
+ Connection conn = ds.getConnection();
+ Statement stmt1 = conn.prepareStatement("select 1 from dual");
+ Statement ustmt1 = ((DelegatingStatement)
stmt1).getInnermostDelegate();
+ Statement cstmt1 = conn.prepareCall("{call home}");
+ Statement ucstmt1 = ((DelegatingStatement)
cstmt1).getInnermostDelegate();
+ stmt1.close(); // Return to pool
+ cstmt1.close(); // ""
+ Statement stmt2 = conn.prepareStatement("select 1 from dual"); //
Check out from pool
+ Statement ustmt2 = ((DelegatingStatement)
stmt2).getInnermostDelegate();
+ Statement cstmt2 = conn.prepareCall("{call home}");
+ Statement ucstmt2 = ((DelegatingStatement)
cstmt2).getInnermostDelegate();
+ stmt2.close(); // Return to pool
+ cstmt2.close(); // ""
+ assertSame(ustmt1, ustmt2);
+ assertSame(ucstmt1, ucstmt2);
+ // Verify key distinguishes Callable from Prepared Statements in the
pool
+ Statement stmt3 = conn.prepareCall("select 1 from dual");
+ Statement ustmt3 = ((DelegatingStatement)
stmt3).getInnermostDelegate();
+ stmt3.close();
+ assertNotSame(ustmt1, ustmt3);
+ assertNotSame(ustmt3, ucstmt1);
+ }
+
public void testClosePool() throws Exception {
new TesterDriver();
ConnectionFactory connFactory = new DriverManagerConnectionFactory(
Modified: commons/proper/dbcp/trunk/xdocs/changes.xml
URL:
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/xdocs/changes.xml?rev=882981&r1=882980&r2=882981&view=diff
==============================================================================
--- commons/proper/dbcp/trunk/xdocs/changes.xml (original)
+++ commons/proper/dbcp/trunk/xdocs/changes.xml Sat Nov 21 20:04:53 2009
@@ -42,6 +42,13 @@
new features as well as bug fixes and instrumentation. Some bug fixes
will change semantics (e.g. connection close will become idempotent).
The minimum JDK level will be increased to 1.4">
+ <action dev="psteitz" type="fix" issue="DBCP-204" due-to="Wei Chen">
+ Made PoolingConnection pool CallableStatements. When BasicDataSource's
+ poolPreparedStatements property is true, CallableStatements are now
+ pooled along with PreparedStatements. The maxOpenPreparedStatements
+ property limits the combined number of Callable and Prepared statements
+ that can be in use at a given time.
+ </action>
<action dev="markt" type="update" issue="DBCP-305" due-to="Christopher
Schultz">
Use an API specific exception for logging abandoned objects to make
scanning the logs for these exceptions simpler and to provide a better