View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.dbcp;
19  
20  import java.sql.CallableStatement;
21  import java.sql.Connection;
22  import java.sql.PreparedStatement;
23  import java.sql.SQLException;
24  
25  import java.util.NoSuchElementException;
26  
27  import org.apache.commons.pool.KeyedObjectPool;
28  import org.apache.commons.pool.KeyedPoolableObjectFactory;
29  
30  /**
31   * A {@link DelegatingConnection} that pools {@link PreparedStatement}s.
32   * <p>
33   * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than creating a new PreparedStatement
34   * each time, may actually pull the statement from a pool of unused statements.
35   * The {@link PreparedStatement#close} method of the returned statement doesn't
36   * actually close the statement, but rather returns it to the pool. 
37   * (See {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.)
38   * 
39   *
40   * @see PoolablePreparedStatement
41   * @author Rodney Waldhoff
42   * @author Dirk Verbeeck
43   * @version $Revision: 883002 $ $Date: 2009-11-21 16:42:48 -0500 (Sat, 21 Nov 2009) $
44   */
45  public class PoolingConnection extends DelegatingConnection implements Connection, KeyedPoolableObjectFactory {
46      /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
47      protected KeyedObjectPool _pstmtPool = null;
48  
49      /** Prepared Statement type */
50      private static final byte STATEMENT_PREPAREDSTMT = 0;
51      
52      /** Callable Statement type */
53      private static final byte STATEMENT_CALLABLESTMT = 1;
54       
55      
56      /**
57       * Constructor.
58       * @param c the underlying {@link Connection}.
59       */
60      public PoolingConnection(Connection c) {
61          super(c);
62      }
63  
64      /**
65       * Constructor.
66       * @param c the underlying {@link Connection}.
67       * @param pool {@link KeyedObjectPool} of {@link PreparedStatement}s and {@link CallableStatement}s.
68       */
69      public PoolingConnection(Connection c, KeyedObjectPool pool) {
70          super(c);
71          _pstmtPool = pool;
72      }
73  
74  
75      /**
76       * Close and free all {@link PreparedStatement}s or {@link CallableStatement} from my pool, and
77       * close my underlying connection.
78       */
79      public synchronized void close() throws SQLException {
80          if(null != _pstmtPool) {
81              KeyedObjectPool oldpool = _pstmtPool;            
82              _pstmtPool = null;
83              try {
84                  oldpool.close();
85              } catch(RuntimeException e) {
86                  throw e;
87              } catch(SQLException e) {
88                  throw e;
89              } catch(Exception e) {
90                  throw (SQLException) new SQLException("Cannot close connection").initCause(e);
91              }
92          }
93          getInnermostDelegate().close();
94      }
95  
96      /**
97       * Create or obtain a {@link PreparedStatement} from my pool.
98       * @return a {@link PoolablePreparedStatement}
99       */
100     public PreparedStatement prepareStatement(String sql) throws SQLException {
101         if (null == _pstmtPool) {
102             throw new SQLException(
103                     "Statement pool is null - closed or invalid PoolingConnection.");
104         }
105         try {
106             return(PreparedStatement)(_pstmtPool.borrowObject(createKey(sql)));
107         } catch(NoSuchElementException e) {
108             throw (SQLException) new SQLException("MaxOpenPreparedStatements limit reached").initCause(e); 
109         } catch(RuntimeException e) {
110             throw e;
111         } catch(Exception e) {
112             throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
113         }
114     }
115 
116     /**
117      * Create or obtain a {@link PreparedStatement} from my pool.
118      * @return a {@link PoolablePreparedStatement}
119      */
120     public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
121         if (null == _pstmtPool) {
122             throw new SQLException(
123                     "Statement pool is null - closed or invalid PoolingConnection.");
124         }
125         try {
126             return(PreparedStatement)(_pstmtPool.borrowObject(createKey(sql,resultSetType,resultSetConcurrency)));
127         } catch(NoSuchElementException e) {
128             throw (SQLException) new SQLException("MaxOpenPreparedStatements limit reached").initCause(e); 
129         } catch(RuntimeException e) {
130             throw e;
131         } catch(Exception e) {
132             throw (SQLException) new SQLException("Borrow prepareStatement from pool failed").initCause(e);
133         }
134     }
135     
136     /**
137      * Create or obtain a {@link CallableStatement} from the pool.
138      * 
139      * @param sql the sql string used to define the CallableStatement
140      * @return a {@link PoolableCallableStatement}
141      * @throws SQLException
142      * @since 1.3
143      */
144     public CallableStatement prepareCall(String sql) throws SQLException {
145         try {
146             return (CallableStatement) (_pstmtPool.borrowObject(createKey(sql, STATEMENT_CALLABLESTMT)));
147         } catch (NoSuchElementException e) {
148             throw new SQLNestedException("MaxOpenCallableStatements limit reached", e);
149         } catch (RuntimeException e) {
150             throw e;
151         } catch (Exception e) {
152             throw new SQLNestedException("Borrow callableStatement from pool failed", e);
153         }
154     }
155     
156     /**
157      * Create or obtain a {@link CallableStatement} from the pool.
158      * 
159      * @param sql the sql string used to define the CallableStatement
160      * @return a {@link PoolableCallableStatement}
161      * @throws SQLException
162      * @since 1.3
163      */
164     public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
165         try {
166             return (CallableStatement) (_pstmtPool.borrowObject(createKey(sql, resultSetType,
167                             resultSetConcurrency, STATEMENT_CALLABLESTMT)));
168         } catch (NoSuchElementException e) {
169             throw new SQLNestedException("MaxOpenCallableStatements limit reached", e);
170         } catch (RuntimeException e) {
171             throw e;
172         } catch (Exception e) {
173             throw new SQLNestedException("Borrow callableStatement from pool failed", e);
174         }
175     }
176     
177 
178     // ------------------- JDBC 3.0 -----------------------------------------
179     // Will be commented by the build process on a JDBC 2.0 system
180 
181 /* JDBC_3_ANT_KEY_BEGIN */
182 
183 //    TODO: possible enhancement, cache these preparedStatements as well
184 
185 //    public PreparedStatement prepareStatement(String sql, int resultSetType,
186 //                                              int resultSetConcurrency,
187 //                                              int resultSetHoldability)
188 //        throws SQLException {
189 //        return super.prepareStatement(
190 //            sql, resultSetType, resultSetConcurrency, resultSetHoldability);
191 //    }
192 //
193 //    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
194 //        throws SQLException {
195 //        return super.prepareStatement(sql, autoGeneratedKeys);
196 //    }
197 //
198 //    public PreparedStatement prepareStatement(String sql, int columnIndexes[])
199 //        throws SQLException {
200 //        return super.prepareStatement(sql, columnIndexes);
201 //    }
202 //
203 //    public PreparedStatement prepareStatement(String sql, String columnNames[])
204 //        throws SQLException {
205 //        return super.prepareStatement(sql, columnNames);
206 //    }
207 
208 /* JDBC_3_ANT_KEY_END */
209 
210 
211     /**
212      * Create a PStmtKey for the given arguments.
213      */
214     protected Object createKey(String sql, int resultSetType, int resultSetConcurrency) {
215         String catalog = null;
216         try {
217             catalog = getCatalog();
218         } catch (SQLException e) {}
219         return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency);
220     }
221     
222     /**
223      * Create a PStmtKey for the given arguments.
224      */
225     protected Object createKey(String sql, int resultSetType, int resultSetConcurrency, byte stmtType) {
226         String catalog = null;
227         try {
228             catalog = getCatalog();
229         } catch (Exception e) {}
230         return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency, stmtType);
231     }
232 
233     /**
234      * Create a PStmtKey for the given arguments.
235      */
236     protected Object createKey(String sql) {
237         String catalog = null;
238         try {
239             catalog = getCatalog();
240         } catch (SQLException e) {}
241         return new PStmtKey(normalizeSQL(sql), catalog);
242     }
243     
244     /**
245      * Create a PStmtKey for the given arguments.
246      */
247     protected Object createKey(String sql, byte stmtType) {
248         String catalog = null;
249         try {
250             catalog = getCatalog();
251         } catch (Exception e) {}
252         return new PStmtKey(normalizeSQL(sql), catalog, stmtType);
253     }
254 
255     /**
256      * Normalize the given SQL statement, producing a
257      * cannonical form that is semantically equivalent to the original.
258      */
259     protected String normalizeSQL(String sql) {
260         return sql.trim();
261     }
262 
263     /**
264      * {@link KeyedPoolableObjectFactory} method for creating
265      * {@link PoolablePreparedStatement}s or {@link PoolableCallableStatements}.
266      * The {@link PStmtKey#_stmtType} field in the key determines whether
267      * a PoolablePreparedStatement or PoolableCallableStatement is created.
268      * 
269      * @param obj the key for the {@link PreparedStatement} to be created
270      */
271     public Object makeObject(Object obj) throws Exception {
272         if(null == obj || !(obj instanceof PStmtKey)) {
273             throw new IllegalArgumentException("Prepared statement key is null or invalid.");
274         } else {
275             PStmtKey key = (PStmtKey)obj;
276             if( null == key._resultSetType && null == key._resultSetConcurrency ) {
277                 return key._stmtType == STATEMENT_PREPAREDSTMT ?
278                     new PoolablePreparedStatement(getDelegate().prepareStatement( key._sql), key, _pstmtPool, this) :
279                     new PoolableCallableStatement(getDelegate().prepareCall( key._sql ), key, _pstmtPool, this);
280 
281             }else {
282                 return key._stmtType == STATEMENT_PREPAREDSTMT ?
283                     new PoolablePreparedStatement(getDelegate().prepareStatement(
284                         key._sql, key._resultSetType.intValue(),key._resultSetConcurrency.intValue()), key, _pstmtPool, this) :
285                     new PoolableCallableStatement( getDelegate().prepareCall(
286                         key._sql,key._resultSetType.intValue(), key._resultSetConcurrency.intValue() ), key, _pstmtPool, this);
287             }
288         }
289     }
290 
291     /**
292      * {@link KeyedPoolableObjectFactory} method for destroying
293      * PoolablePreparedStatements and PoolableCallableStatements.
294      * Closes the underlying statement.
295      * 
296      * @param key ignored
297      * @param obj the pooled statement to be destroyed.
298      */
299     public void destroyObject(Object key, Object obj) throws Exception {
300         if(obj instanceof DelegatingPreparedStatement) {
301             ((DelegatingPreparedStatement)obj).getInnermostDelegate().close();
302         } else {
303             ((PreparedStatement)obj).close();
304         }
305     }
306 
307     /**
308      * {@link KeyedPoolableObjectFactory} method for validating
309      * pooled statements. Currently always returns true.
310      * 
311      * @param key ignored
312      * @param obj ignored
313      * @return <tt>true</tt>
314      */
315     public boolean validateObject(Object key, Object obj) {
316         return true;
317     }
318 
319     /**
320      * {@link KeyedPoolableObjectFactory} method for activating
321      * pooled statements.
322      * 
323      * @param key ignored
324      * @param obj pooled statement to be activated
325      */
326     public void activateObject(Object key, Object obj) throws Exception {
327         ((DelegatingPreparedStatement)obj).activate();
328     }
329 
330     /**
331      * {@link KeyedPoolableObjectFactory} method for passivating
332      * {@link PreparedStatement}s or {@link CallableStatement}s.
333      * Invokes {@link PreparedStatement#clearParameters}.
334      * 
335      * @param key ignored
336      * @param obj a {@link PreparedStatement}
337      */
338     public void passivateObject(Object key, Object obj) throws Exception {
339         ((PreparedStatement)obj).clearParameters();
340         ((DelegatingPreparedStatement)obj).passivate();
341     }
342 
343     public String toString() {
344         if (_pstmtPool != null ) {
345             return "PoolingConnection: " + _pstmtPool.toString();
346         } else {
347             return "PoolingConnection: null";
348         }
349     }
350 
351     /**
352      * A key uniquely identifiying {@link PreparedStatement}s.
353      */
354     static class PStmtKey {
355         
356         /** SQL defining Prepared or Callable Statement */
357         protected String _sql = null;
358         
359         /** Result set type */
360         protected Integer _resultSetType = null;
361         
362         /** Result set concurrency */
363         protected Integer _resultSetConcurrency = null;
364         
365         /** Database catalog */
366         protected String _catalog = null;
367         
368         /** 
369          *  Statement type. Either STATEMENT_PREPAREDSTMT (PreparedStatement)
370          *  or STATEMENT_CALLABLESTMT (CallableStatement) 
371          */
372         protected byte _stmtType = STATEMENT_PREPAREDSTMT;
373         
374         PStmtKey(String sql) {
375             _sql = sql;
376         }
377 
378         PStmtKey(String sql, String catalog) {
379             _sql = sql;
380             _catalog = catalog;
381         }
382         
383         PStmtKey(String sql, String catalog, byte stmtType) {
384             _sql = sql;
385             _catalog = catalog;
386             _stmtType = stmtType;
387         }
388 
389         PStmtKey(String sql, int resultSetType, int resultSetConcurrency) {
390             _sql = sql;
391             _resultSetType = new Integer(resultSetType);
392             _resultSetConcurrency = new Integer(resultSetConcurrency);
393         }
394 
395         PStmtKey(String sql, String catalog, int resultSetType, int resultSetConcurrency) {
396             _sql = sql;
397             _catalog = catalog;
398             _resultSetType = new Integer(resultSetType);
399             _resultSetConcurrency = new Integer(resultSetConcurrency);
400         }
401         
402         PStmtKey(String sql, String catalog, int resultSetType, int resultSetConcurrency, byte stmtType) {
403             _sql = sql;
404             _catalog = catalog;
405             _resultSetType = new Integer(resultSetType);
406             _resultSetConcurrency = new Integer(resultSetConcurrency);
407             _stmtType = stmtType;
408         }
409 
410         public boolean equals(Object that) {
411             try {
412                 PStmtKey key = (PStmtKey)that;
413                 return( ((null == _sql && null == key._sql) || _sql.equals(key._sql)) &&
414                         ((null == _catalog && null == key._catalog) || _catalog.equals(key._catalog)) &&
415                         ((null == _resultSetType && null == key._resultSetType) || _resultSetType.equals(key._resultSetType)) &&
416                         ((null == _resultSetConcurrency && null == key._resultSetConcurrency) || _resultSetConcurrency.equals(key._resultSetConcurrency)) &&
417                         (_stmtType == key._stmtType)
418                       );
419             } catch(ClassCastException e) {
420                 return false;
421             } catch(NullPointerException e) {
422                 return false;
423             }
424         }
425 
426         public int hashCode() {
427             if (_catalog==null)
428                 return(null == _sql ? 0 : _sql.hashCode());
429             else
430                 return(null == _sql ? _catalog.hashCode() : (_catalog + _sql).hashCode());
431         }
432 
433         public String toString() {
434             StringBuffer buf = new StringBuffer();
435             buf.append("PStmtKey: sql=");
436             buf.append(_sql);
437             buf.append(", catalog=");
438             buf.append(_catalog);
439             buf.append(", resultSetType=");
440             buf.append(_resultSetType);
441             buf.append(", resultSetConcurrency=");
442             buf.append(_resultSetConcurrency);
443             buf.append(", statmentType=");
444             buf.append(_stmtType);
445             return buf.toString();
446         }
447     }
448 }