Repository: commons-dbcp Updated Branches: refs/heads/master b3654dbd3 -> 735d9a839
[DBCP-496] Add support for pooling CallableStatements to the org.apache.commons.dbcp2.cpdsadapter package. Project: http://git-wip-us.apache.org/repos/asf/commons-dbcp/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-dbcp/commit/735d9a83 Tree: http://git-wip-us.apache.org/repos/asf/commons-dbcp/tree/735d9a83 Diff: http://git-wip-us.apache.org/repos/asf/commons-dbcp/diff/735d9a83 Branch: refs/heads/master Commit: 735d9a839110590ff1c82ff619cbd7facb34d078 Parents: b3654db Author: Gary Gregory <garydgreg...@gmail.com> Authored: Fri Jun 8 22:20:34 2018 -0600 Committer: Gary Gregory <garydgreg...@gmail.com> Committed: Fri Jun 8 22:20:34 2018 -0600 ---------------------------------------------------------------------- src/changes/changes.xml | 3 + .../commons/dbcp2/DelegatingStatement.java | 14 +- .../dbcp2/PoolableCallableStatement.java | 8 +- .../apache/commons/dbcp2/PoolingConnection.java | 28 +- .../dbcp2/cpdsadapter/ConnectionImpl.java | 93 +++++++ .../dbcp2/cpdsadapter/DriverAdapterCPDS.java | 9 +- .../dbcp2/cpdsadapter/PooledConnectionImpl.java | 267 ++++++++++++++----- .../datasources/TestSharedPoolDataSource.java | 147 +++++++++- 8 files changed, 482 insertions(+), 87 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-dbcp/blob/735d9a83/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index feeb3a6..87569dc 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -76,6 +76,9 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="update" issue="DBCP-495" due-to="Gary Gregory"> Remove duplicate code in org.apache.commons.dbcp2.cpdsadapter.PStmtKeyCPDS. </action> + <action dev="ggregory" type="fix" issue="DBCP-496" due-to="Gary Gregory"> + Add support for pooling CallableStatements to the org.apache.commons.dbcp2.cpdsadapter package. + </action> </release> <release version="2.3.0" date="2018-05-12" description="This is a minor release, including bug fixes and enhancements."> <action dev="pschumacher" type="fix" issue="DBCP-476" due-to="Gary Evesson, Richard Cordova"> http://git-wip-us.apache.org/repos/asf/commons-dbcp/blob/735d9a83/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java b/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java index 7ad0ddf..46671d5 100644 --- a/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java +++ b/src/main/java/org/apache/commons/dbcp2/DelegatingStatement.java @@ -174,13 +174,23 @@ public class DelegatingStatement extends AbandonedTrace implements Statement { } } - protected void activate() throws SQLException { + /** + * + * @throws SQLException + * @since 2.4.0 made public, was protected in 2.3.0. + */ + public void activate() throws SQLException { if(_stmt instanceof DelegatingStatement) { ((DelegatingStatement)_stmt).activate(); } } - protected void passivate() throws SQLException { + /** + * + * @throws SQLException + * @since 2.4.0 made public, was protected in 2.3.0. + */ + public void passivate() throws SQLException { if(_stmt instanceof DelegatingStatement) { ((DelegatingStatement)_stmt).passivate(); } http://git-wip-us.apache.org/repos/asf/commons-dbcp/blob/735d9a83/src/main/java/org/apache/commons/dbcp2/PoolableCallableStatement.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbcp2/PoolableCallableStatement.java b/src/main/java/org/apache/commons/dbcp2/PoolableCallableStatement.java index e1e4e1d..023da51 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolableCallableStatement.java +++ b/src/main/java/org/apache/commons/dbcp2/PoolableCallableStatement.java @@ -90,9 +90,11 @@ public class PoolableCallableStatement extends DelegatingCallableStatement { /** * Activates after retrieval from the pool. Adds a trace for this CallableStatement to the Connection * that created it. + * + * @since 2.4.0 made public, was protected in 2.3.0. */ @Override - protected void activate() throws SQLException { + public void activate() throws SQLException { setClosedInternal(false); if (getConnectionInternal() != null) { getConnectionInternal().addTrace(this); @@ -103,9 +105,11 @@ public class PoolableCallableStatement extends DelegatingCallableStatement { /** * 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. + * + * @since 2.4.0 made public, was protected in 2.3.0. */ @Override - protected void passivate() throws SQLException { + public void passivate() throws SQLException { setClosedInternal(true); if (getConnectionInternal() != null) { getConnectionInternal().removeTrace(this); http://git-wip-us.apache.org/repos/asf/commons-dbcp/blob/735d9a83/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java index 8f72b31..8745950 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java +++ b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java @@ -38,34 +38,42 @@ import org.apache.commons.pool2.impl.DefaultPooledObject; * actually close the statement, but rather returns it to the pool. * (See {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.) * - * * @see PoolablePreparedStatement * @author Rodney Waldhoff * @author Dirk Verbeeck * @since 2.0 */ public class PoolingConnection extends DelegatingConnection<Connection> - implements KeyedPooledObjectFactory<PStmtKey,DelegatingPreparedStatement> { + implements KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> { /** - * The possible statement types. - * @since 2.0 + * Statement types. + * + * @since 2.0 protected enum. + * @since 2.4.0 public enum. */ - protected static enum StatementType { + public enum StatementType { + + /** + * Callable statement. + */ CALLABLE_STATEMENT, + + /** + * Prepared statement. + */ PREPARED_STATEMENT } /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */ - private KeyedObjectPool<PStmtKey,DelegatingPreparedStatement> _pstmtPool = null; - + private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> _pstmtPool = null; /** * Constructor. - * @param c the underlying {@link Connection}. + * @param conn the underlying {@link Connection}. */ - public PoolingConnection(final Connection c) { - super(c); + public PoolingConnection(final Connection conn) { + super(conn); } http://git-wip-us.apache.org/repos/asf/commons-dbcp/blob/735d9a83/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java index 4d1c416..adcc8ef 100644 --- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java +++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/ConnectionImpl.java @@ -17,10 +17,12 @@ package org.apache.commons.dbcp2.cpdsadapter; +import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import org.apache.commons.dbcp2.DelegatingCallableStatement; import org.apache.commons.dbcp2.DelegatingConnection; import org.apache.commons.dbcp2.DelegatingPreparedStatement; @@ -84,6 +86,97 @@ class ConnectionImpl extends DelegatingConnection<Connection> { } /** + * If pooling of <code>CallableStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may + * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. + * + * @param sql + * an SQL statement that may contain one or more '?' parameter placeholders. Typically this statement is + * specified using JDBC call escape syntax. + * @return a default <code>CallableStatement</code> object containing the pre-compiled SQL statement. + * @exception SQLException + * Thrown if a database access error occurs or this method is called on a closed connection. + * @since 2.4.0 + */ + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + checkOpen(); + try { + return new DelegatingCallableStatement(this, pooledConnection.prepareCall(sql)); + } catch (final SQLException e) { + handleException(e); // Does not return + return null; + } + } + + /** + * If pooling of <code>CallableStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may + * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. + * + * @param sql + * a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or + * more '?' parameters. + * @param resultSetType + * a result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>, + * <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>. + * @param resultSetConcurrency + * a concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or + * <code>ResultSet.CONCUR_UPDATABLE</code>. + * @return a <code>CallableStatement</code> object containing the pre-compiled SQL statement that will produce + * <code>ResultSet</code> objects with the given type and concurrency. + * @throws SQLException + * Thrown if a database access error occurs, this method is called on a closed connection or the given + * parameters are not <code>ResultSet</code> constants indicating type and concurrency. + * @since 2.4.0 + */ + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + checkOpen(); + try { + return new DelegatingCallableStatement(this, + pooledConnection.prepareCall(sql, resultSetType, resultSetConcurrency)); + } catch (final SQLException e) { + handleException(e); // Does not return + return null; + } + } + + /** + * If pooling of <code>CallableStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may + * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. + * + * @param sql + * a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or + * more '?' parameters. + * @param resultSetType + * one of the following <code>ResultSet</code> constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>, + * <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>. + * @param resultSetConcurrency + * one of the following <code>ResultSet</code> constants: <code>ResultSet.CONCUR_READ_ONLY</code> or + * <code>ResultSet.CONCUR_UPDATABLE</code>. + * @param resultSetHoldability + * one of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> + * or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>. + * @return a new <code>CallableStatement</code> object, containing the pre-compiled SQL statement, that will + * generate <code>ResultSet</code> objects with the given type, concurrency, and holdability. + * @throws SQLException + * Thrown if a database access error occurs, this method is called on a closed connection or the given + * parameters are not <code>ResultSet</code> constants indicating type, concurrency, and holdability. + * @since 2.4.0 + */ + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + checkOpen(); + try { + return new DelegatingCallableStatement(this, + pooledConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); + } catch (final SQLException e) { + handleException(e); // Does not return + return null; + } + } + + /** * If pooling of <code>PreparedStatement</code>s is turned on in the * {@link DriverAdapterCPDS}, a pooled object may be returned, otherwise * delegate to the wrapped JDBC 1.x {@link java.sql.Connection}. http://git-wip-us.apache.org/repos/asf/commons-dbcp/blob/735d9a83/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java index db811b7..e16d45c 100644 --- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java +++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java @@ -37,7 +37,8 @@ import javax.naming.spi.ObjectFactory; import javax.sql.ConnectionPoolDataSource; import javax.sql.PooledConnection; -import org.apache.commons.dbcp2.PoolablePreparedStatement; +import org.apache.commons.dbcp2.DelegatingPreparedStatement; +import org.apache.commons.dbcp2.PStmtKey; import org.apache.commons.pool2.KeyedObjectPool; import org.apache.commons.pool2.impl.BaseObjectPoolConfig; import org.apache.commons.pool2.impl.GenericKeyedObjectPool; @@ -186,9 +187,7 @@ public class DriverAdapterCPDS getUrl(), username, pass)); } pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); - } - catch (final ClassCircularityError e) - { + } catch (final ClassCircularityError e) { if (connectionProperties != null) { pci = new PooledConnectionImpl(DriverManager.getConnection( getUrl(), connectionProperties)); @@ -198,7 +197,7 @@ public class DriverAdapterCPDS } pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); } - KeyedObjectPool<PStmtKeyCPDS, PoolablePreparedStatement<PStmtKeyCPDS>> stmtPool = null; + KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = null; if (isPoolPreparedStatements()) { final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig(); config.setMaxTotalPerKey(Integer.MAX_VALUE); http://git-wip-us.apache.org/repos/asf/commons-dbcp/blob/735d9a83/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java index fa46cbf..ca41608 100644 --- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java +++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java @@ -17,6 +17,7 @@ package org.apache.commons.dbcp2.cpdsadapter; +import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -28,7 +29,11 @@ import javax.sql.PooledConnection; import javax.sql.StatementEventListener; import org.apache.commons.dbcp2.DelegatingConnection; +import org.apache.commons.dbcp2.DelegatingPreparedStatement; +import org.apache.commons.dbcp2.PStmtKey; +import org.apache.commons.dbcp2.PoolableCallableStatement; import org.apache.commons.dbcp2.PoolablePreparedStatement; +import org.apache.commons.dbcp2.PoolingConnection.StatementType; import org.apache.commons.pool2.KeyedObjectPool; import org.apache.commons.pool2.KeyedPooledObjectFactory; import org.apache.commons.pool2.PooledObject; @@ -42,7 +47,7 @@ import org.apache.commons.pool2.impl.DefaultPooledObject; * @since 2.0 */ class PooledConnectionImpl - implements PooledConnection, KeyedPooledObjectFactory<PStmtKeyCPDS, PoolablePreparedStatement<PStmtKeyCPDS>> { + implements PooledConnection, KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> { private static final String CLOSED = "Attempted to use PooledConnection after closed() was called."; @@ -74,12 +79,12 @@ class PooledConnectionImpl new Vector<>(); /** - * flag set to true, once close() is called. + * Flag set to true, once close() is called. */ private boolean isClosed; /** My pool of {@link PreparedStatement}s. */ - private KeyedObjectPool<PStmtKeyCPDS, PoolablePreparedStatement<PStmtKeyCPDS>> pstmtPool = null; + private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pstmtPool; /** * Controls access to the underlying connection @@ -87,7 +92,7 @@ class PooledConnectionImpl private boolean accessToUnderlyingConnectionAllowed = false; /** - * Wrap the real connection. + * Wraps the real connection. * @param connection the connection to be wrapped */ PooledConnectionImpl(final Connection connection) { @@ -108,8 +113,8 @@ class PooledConnectionImpl * @param p ignored */ @Override - public void activateObject(final PStmtKeyCPDS key, - final PooledObject<PoolablePreparedStatement<PStmtKeyCPDS>> p) + public void activateObject(final PStmtKey key, + final PooledObject<DelegatingPreparedStatement> p) throws Exception { p.getObject().activate(); } @@ -175,52 +180,75 @@ class PooledConnectionImpl } /** - * Create a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. */ - protected PStmtKeyCPDS createKey(final String sql) { - return new PStmtKeyCPDS(normalizeSQL(sql)); + protected PStmtKey createKey(final String sql) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull()); } /** - * Create a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. */ - protected PStmtKeyCPDS createKey(final String sql, final int autoGeneratedKeys) { - return new PStmtKeyCPDS(normalizeSQL(sql), autoGeneratedKeys); + protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), autoGeneratedKeys); } /** - * Create a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. */ - protected PStmtKeyCPDS createKey(final String sql, final int columnIndexes[]) { - return new PStmtKeyCPDS(normalizeSQL(sql), columnIndexes); + protected PStmtKey createKey(final String sql, final int columnIndexes[]) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), columnIndexes); } /** - * Create a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. */ - protected PStmtKeyCPDS createKey(final String sql, final int resultSetType, - final int resultSetConcurrency) { - return new PStmtKeyCPDS(normalizeSQL(sql), resultSetType, - resultSetConcurrency); + protected PStmtKey createKey(final String sql, final int resultSetType, + final int resultSetConcurrency) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), resultSetType, resultSetConcurrency); } /** - * Create a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. */ - protected PStmtKeyCPDS createKey(final String sql, final int resultSetType, + protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) { - return new PStmtKeyCPDS(normalizeSQL(sql), resultSetType, + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability); } - // ------------------------------------------------------------------- - // The following code implements a PreparedStatement pool + /** + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + * + * @since 2.4.0 + */ + protected PStmtKey createKey(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability, + StatementType statementType) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability, + statementType); + } + /** + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + * + * @since 2.4.0 + */ + protected PStmtKey createKey(String sql, int resultSetType, int resultSetConcurrency, + StatementType statementType) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), resultSetType, resultSetConcurrency, statementType); + } + + /** + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + */ + protected PStmtKey createKey(final String sql, final StatementType statementType) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), statementType); + } /** - * Create a {@link PooledConnectionImpl.PStmtKey} for the given arguments. + * Creates a {@link PooledConnectionImpl.PStmtKey} for the given arguments. */ - protected PStmtKeyCPDS createKey(final String sql, final String columnNames[]) { - return new PStmtKeyCPDS(normalizeSQL(sql), columnNames); + protected PStmtKey createKey(final String sql, final String columnNames[]) { + return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), columnNames); } /** @@ -230,8 +258,8 @@ class PooledConnectionImpl * @param p the wrapped {@link PreparedStatement} to be destroyed. */ @Override - public void destroyObject(final PStmtKeyCPDS key, - final PooledObject<PoolablePreparedStatement<PStmtKeyCPDS>> p) + public void destroyObject(final PStmtKey key, + final PooledObject<DelegatingPreparedStatement> p) throws Exception { p.getObject().getInnermostDelegate().close(); } @@ -257,6 +285,14 @@ class PooledConnectionImpl } } + private String getCatalogOrNull() { + try { + return connection == null ? null : connection.getCatalog(); + } catch (SQLException e) { + return null; + } + } + /** * Returns a JDBC connection. * @@ -294,33 +330,49 @@ class PooledConnectionImpl * {@link PreparedStatement}s. * @param key the key for the {@link PreparedStatement} to be created */ + @SuppressWarnings("resource") @Override - public PooledObject<PoolablePreparedStatement<PStmtKeyCPDS>> makeObject(final PStmtKeyCPDS key) throws Exception { + public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws Exception { +// if (null == key) { +// throw new IllegalArgumentException(); +// } +// // _openPstmts++; +// if (null == key.getResultSetType() +// && null == key.getResultSetConcurrency()) { +// if (null == key.getAutoGeneratedKeys()) { +// return new DefaultPooledObject<>(new PoolablePreparedStatement<>( +// connection.prepareStatement(key.getSql()), +// key, pstmtPool, delegatingConnection)); +// } +// return new DefaultPooledObject<>(new PoolablePreparedStatement<>( +// connection.prepareStatement(key.getSql(), +// key.getAutoGeneratedKeys().intValue()), +// key, pstmtPool, delegatingConnection)); +// } +// return new DefaultPooledObject<>(new PoolablePreparedStatement<>( +// connection.prepareStatement(key.getSql(), +// key.getResultSetType().intValue(), +// key.getResultSetConcurrency().intValue()), +// key, pstmtPool, delegatingConnection)); + // + if (null == key) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Prepared statement key is null or invalid."); } - // _openPstmts++; - if (null == key.getResultSetType() - && null == key.getResultSetConcurrency()) { - if (null == key.getAutoGeneratedKeys()) { - return new DefaultPooledObject<>(new PoolablePreparedStatement<>( - connection.prepareStatement(key.getSql()), - key, pstmtPool, delegatingConnection)); - } - return new DefaultPooledObject<>(new PoolablePreparedStatement<>( - connection.prepareStatement(key.getSql(), - key.getAutoGeneratedKeys().intValue()), - key, pstmtPool, delegatingConnection)); + if (key.getStmtType() == StatementType.PREPARED_STATEMENT ) { + final PreparedStatement statement = (PreparedStatement) key.createStatement(connection); + @SuppressWarnings({"rawtypes", "unchecked"}) // Unable to find way to avoid this + final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pstmtPool, delegatingConnection); + return new DefaultPooledObject<DelegatingPreparedStatement>(pps); } - return new DefaultPooledObject<>(new PoolablePreparedStatement<>( - connection.prepareStatement(key.getSql(), - key.getResultSetType().intValue(), - key.getResultSetConcurrency().intValue()), - key, pstmtPool, delegatingConnection)); + final CallableStatement statement = (CallableStatement) key.createStatement(connection); + final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pstmtPool, + (DelegatingConnection<Connection>) delegatingConnection); + return new DefaultPooledObject<DelegatingPreparedStatement>(pcs); } /** - * Normalize the given SQL statement, producing a + * Normalizes the given SQL statement, producing a * canonical form that is semantically equivalent to the original. */ protected String normalizeSQL(final String sql) { @@ -345,16 +397,109 @@ class PooledConnectionImpl * @param p a wrapped {@link PreparedStatement} */ @Override - public void passivateObject(final PStmtKeyCPDS key, - final PooledObject<PoolablePreparedStatement<PStmtKeyCPDS>> p) + public void passivateObject(final PStmtKey key, + final PooledObject<DelegatingPreparedStatement> p) throws Exception { - final PoolablePreparedStatement<PStmtKeyCPDS> ppss = p.getObject(); - ppss.clearParameters(); - ppss.passivate(); + @SuppressWarnings("resource") + final DelegatingPreparedStatement dps = p.getObject(); + dps.clearParameters(); + dps.passivate(); + } + + /** + * Creates or obtains a {@link CallableStatement} from my pool. + * + * @param sql + * an SQL statement that may contain one or more '?' parameter placeholders. Typically this statement is + * specified using JDBC call escape syntax. + * @return a default <code>CallableStatement</code> object containing the pre-compiled SQL statement. + * @exception SQLException + * Thrown if a database access error occurs or this method is called on a closed connection. + * @since 2.4.0 + */ + CallableStatement prepareCall(String sql) throws SQLException { + if (pstmtPool == null) { + return connection.prepareCall(sql); + } + try { + return (CallableStatement) pstmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareCall from pool failed", e); + } + } + + /** + * Creates or obtains a {@link CallableStatement} from my pool. + * + * @param sql + * a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or + * more '?' parameters. + * @param resultSetType + * a result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>, + * <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>. + * @param resultSetConcurrency + * a concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or + * <code>ResultSet.CONCUR_UPDATABLE</code>. + * @return a <code>CallableStatement</code> object containing the pre-compiled SQL statement that will produce + * <code>ResultSet</code> objects with the given type and concurrency. + * @throws SQLException + * Thrown if a database access error occurs, this method is called on a closed connection or the given + * parameters are not <code>ResultSet</code> constants indicating type and concurrency. + * @since 2.4.0 + */ + CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + if (pstmtPool == null) { + return connection.prepareCall(sql, resultSetType, resultSetConcurrency); + } + try { + return (CallableStatement) pstmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareCall from pool failed", e); + } + } + + /** + * Creates or obtains a {@link CallableStatement} from my pool. + * + * @param sql + * a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or + * more '?' parameters. + * @param resultSetType + * one of the following <code>ResultSet</code> constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>, + * <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>. + * @param resultSetConcurrency + * one of the following <code>ResultSet</code> constants: <code>ResultSet.CONCUR_READ_ONLY</code> or + * <code>ResultSet.CONCUR_UPDATABLE</code>. + * @param resultSetHoldability + * one of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> + * or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>. + * @return a new <code>CallableStatement</code> object, containing the pre-compiled SQL statement, that will + * generate <code>ResultSet</code> objects with the given type, concurrency, and holdability. + * @throws SQLException + * Thrown if a database access error occurs, this method is called on a closed connection or the given + * parameters are not <code>ResultSet</code> constants indicating type, concurrency, and holdability. + * @since 2.4.0 + */ + CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + if (pstmtPool == null) { + return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + try { + return (CallableStatement) pstmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability, StatementType.CALLABLE_STATEMENT)); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new SQLException("Borrow prepareCall from pool failed", e); + } } /** - * Create or obtain a {@link PreparedStatement} from my pool. + * Creates or obtains a {@link PreparedStatement} from my pool. * @param sql the SQL statement * @return a {@link PoolablePreparedStatement} */ @@ -372,7 +517,7 @@ class PooledConnectionImpl } /** - * Create or obtain a {@link PreparedStatement} from my pool. + * Creates or obtains a {@link PreparedStatement} from my pool. * @param sql an SQL statement that may contain one or more '?' IN * parameter placeholders * @param autoGeneratedKeys a flag indicating whether auto-generated keys @@ -411,7 +556,7 @@ class PooledConnectionImpl } /** - * Create or obtain a {@link PreparedStatement} from my pool. + * Creates or obtains a {@link PreparedStatement} from my pool. * @param sql a <code>String</code> object that is the SQL statement to * be sent to the database; may contain one or more '?' IN * parameters @@ -501,7 +646,7 @@ class PooledConnectionImpl } public void setStatementPool( - final KeyedObjectPool<PStmtKeyCPDS, PoolablePreparedStatement<PStmtKeyCPDS>> statementPool) { + final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> statementPool) { pstmtPool = statementPool; } @@ -513,8 +658,8 @@ class PooledConnectionImpl * @return {@code true} */ @Override - public boolean validateObject(final PStmtKeyCPDS key, - final PooledObject<PoolablePreparedStatement<PStmtKeyCPDS>> p) { + public boolean validateObject(final PStmtKey key, + final PooledObject<DelegatingPreparedStatement> p) { return true; } } http://git-wip-us.apache.org/repos/asf/commons-dbcp/blob/735d9a83/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java b/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java index e41fdcd..f436b6c 100644 --- a/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java +++ b/src/test/java/org/apache/commons/dbcp2/datasources/TestSharedPoolDataSource.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -397,6 +398,22 @@ public class TestSharedPoolDataSource extends TestConnectionPool { // Bugzilla Bug 24136 ClassCastException in DriverAdapterCPDS // when setPoolPreparedStatements(true) @Test + public void testPoolPrepareCall() throws Exception { + pcds.setPoolPreparedStatements(true); + + final Connection conn = ds.getConnection(); + assertNotNull(conn); + final PreparedStatement stmt = conn.prepareCall("{call home()}"); + assertNotNull(stmt); + final ResultSet rset = stmt.executeQuery(); + assertNotNull(rset); + assertTrue(rset.next()); + rset.close(); + stmt.close(); + conn.close(); + } + + @Test public void testPoolPrepareStatement() throws Exception { pcds.setPoolPreparedStatements(true); @@ -412,6 +429,37 @@ public class TestSharedPoolDataSource extends TestConnectionPool { conn.close(); } + // There are 3 different prepareCall statement methods so add a little + // complexity to reduce what would otherwise be lots of copy and paste + private static abstract class PrepareCallCallback { + protected Connection conn; + void setConnection(final Connection conn) { + this.conn = conn; + } + abstract CallableStatement getCallableStatement() throws SQLException; + } + + private static class CscbString extends PrepareCallCallback { + @Override + CallableStatement getCallableStatement() throws SQLException { + return conn.prepareCall("{call home()}"); + } + } + + private static class CscbStringIntInt extends PrepareCallCallback { + @Override + CallableStatement getCallableStatement() throws SQLException { + return conn.prepareCall("{call home()}", 0, 0); + } + } + + private static class CscbStringIntIntInt extends PrepareCallCallback { + @Override + CallableStatement getCallableStatement() throws SQLException { + return conn.prepareCall("{call home()}", 0, 0, 0); + } + } + // There are 6 different prepareStatement statement methods so add a little // complexity to reduce what would otherwise be lots of copy and paste private static abstract class PrepareStatementCallback { @@ -421,49 +469,127 @@ public class TestSharedPoolDataSource extends TestConnectionPool { } abstract PreparedStatement getPreparedStatement() throws SQLException; } - + private static class PscbString extends PrepareStatementCallback { @Override PreparedStatement getPreparedStatement() throws SQLException { return conn.prepareStatement("select * from dual"); } } - + private static class PscbStringIntInt extends PrepareStatementCallback { @Override PreparedStatement getPreparedStatement() throws SQLException { return conn.prepareStatement("select * from dual", 0, 0); } } - + private static class PscbStringInt extends PrepareStatementCallback { @Override PreparedStatement getPreparedStatement() throws SQLException { return conn.prepareStatement("select * from dual", 0); } } - + private static class PscbStringIntArray extends PrepareStatementCallback { @Override PreparedStatement getPreparedStatement() throws SQLException { return conn.prepareStatement("select * from dual", new int[0]); } } - + private static class PscbStringStringArray extends PrepareStatementCallback { @Override PreparedStatement getPreparedStatement() throws SQLException { return conn.prepareStatement("select * from dual", new String[0]); } } - + private static class PscbStringIntIntInt extends PrepareStatementCallback { @Override PreparedStatement getPreparedStatement() throws SQLException { return conn.prepareStatement("select * from dual", 0, 0, 0); } } - + + private void doTestPoolCallableStatements(final PrepareCallCallback callBack) + throws Exception { + final DriverAdapterCPDS myPcds = new DriverAdapterCPDS(); + DataSource myDs = null; + myPcds.setDriver("org.apache.commons.dbcp2.TesterDriver"); + myPcds.setUrl("jdbc:apache:commons:testdriver"); + myPcds.setUser("foo"); + myPcds.setPassword("bar"); + myPcds.setPoolPreparedStatements(true); + myPcds.setMaxPreparedStatements(10); + + final SharedPoolDataSource spDs = new SharedPoolDataSource(); + spDs.setConnectionPoolDataSource(myPcds); + spDs.setMaxTotal(getMaxTotal()); + spDs.setDefaultMaxWaitMillis((int) getMaxWaitMillis()); + spDs.setDefaultTransactionIsolation( + Connection.TRANSACTION_READ_COMMITTED); + + myDs = spDs; + + Connection conn = ds.getConnection(); + callBack.setConnection(conn); + CallableStatement stmt = null; + ResultSet rset = null; + + assertNotNull(conn); + + stmt = callBack.getCallableStatement(); + assertNotNull(stmt); + final long l1HashCode = ((DelegatingStatement) stmt).getDelegate().hashCode(); + rset = stmt.executeQuery(); + assertNotNull(rset); + assertTrue(rset.next()); + rset.close(); + stmt.close(); + + stmt = callBack.getCallableStatement(); + assertNotNull(stmt); + final long l2HashCode = ((DelegatingStatement) stmt).getDelegate().hashCode(); + rset = stmt.executeQuery(); + assertNotNull(rset); + assertTrue(rset.next()); + rset.close(); + stmt.close(); + + // statement pooling is not enabled, we should get different statements + assertTrue(l1HashCode != l2HashCode); + conn.close(); + conn = null; + + conn = myDs.getConnection(); + callBack.setConnection(conn); + + stmt = callBack.getCallableStatement(); + assertNotNull(stmt); + final long l3HashCode = ((DelegatingStatement) stmt).getDelegate().hashCode(); + rset = stmt.executeQuery(); + assertNotNull(rset); + assertTrue(rset.next()); + rset.close(); + stmt.close(); + + stmt = callBack.getCallableStatement(); + assertNotNull(stmt); + final long l4HashCode = ((DelegatingStatement) stmt).getDelegate().hashCode(); + rset = stmt.executeQuery(); + assertNotNull(rset); + assertTrue(rset.next()); + rset.close(); + stmt.close(); + + // prepared statement pooling is working + assertTrue(l3HashCode == l4HashCode); + conn.close(); + conn = null; + spDs.close(); + } + private void doTestPoolPreparedStatements(final PrepareStatementCallback callBack) throws Exception { final DriverAdapterCPDS mypcds = new DriverAdapterCPDS(); @@ -543,6 +669,13 @@ public class TestSharedPoolDataSource extends TestConnectionPool { } @Test + public void testPoolPreparedCalls() throws Exception { + doTestPoolCallableStatements(new CscbString()); + doTestPoolCallableStatements(new CscbStringIntInt()); + doTestPoolCallableStatements(new CscbStringIntIntInt()); + } + + @Test public void testPoolPreparedStatements() throws Exception { doTestPoolPreparedStatements(new PscbString()); doTestPoolPreparedStatements(new PscbStringIntInt());