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: 1023401 $ $Date: 2010-10-16 21:54:24 -0400 (Sat, 16 Oct 2010) $
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 the pool, and
77       * close the 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 the pool.
98       * @param sql the sql string used to define the PreparedStatement
99       * @return a {@link PoolablePreparedStatement}
100      */
101     public PreparedStatement prepareStatement(String sql) throws SQLException {
102         if (null == _pstmtPool) {
103             throw new SQLException(
104                     "Statement pool is null - closed or invalid PoolingConnection.");
105         }
106         try {
107             return(PreparedStatement)(_pstmtPool.borrowObject(createKey(sql)));
108         } catch(NoSuchElementException e) {
109             throw (SQLException) new SQLException("MaxOpenPreparedStatements limit reached").initCause(e); 
110         } catch(RuntimeException e) {
111             throw e;
112         } catch(Exception e) {
113             throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
114         }
115     }
116 
117     /**
118      * Create or obtain a {@link PreparedStatement} from the pool.
119      * @param sql the sql string used to define the PreparedStatement
120      * @param resultSetType result set type
121      * @param resultSetConcurrency result set concurrency
122      * @return a {@link PoolablePreparedStatement}
123      */
124     public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
125         if (null == _pstmtPool) {
126             throw new SQLException(
127                     "Statement pool is null - closed or invalid PoolingConnection.");
128         }
129         try {
130             return(PreparedStatement)(_pstmtPool.borrowObject(createKey(sql,resultSetType,resultSetConcurrency)));
131         } catch(NoSuchElementException e) {
132             throw (SQLException) new SQLException("MaxOpenPreparedStatements limit reached").initCause(e); 
133         } catch(RuntimeException e) {
134             throw e;
135         } catch(Exception e) {
136             throw (SQLException) new SQLException("Borrow prepareStatement from pool failed").initCause(e);
137         }
138     }
139     
140     /**
141      * Create or obtain a {@link CallableStatement} from the pool.
142      * @param sql the sql string used to define the CallableStatement
143      * @return a {@link PoolableCallableStatement}
144      * @throws SQLException
145      * @since 1.3
146      */
147     public CallableStatement prepareCall(String sql) throws SQLException {
148         try {
149             return (CallableStatement) (_pstmtPool.borrowObject(createKey(sql, STATEMENT_CALLABLESTMT)));
150         } catch (NoSuchElementException e) {
151             throw new SQLNestedException("MaxOpenCallableStatements limit reached", e);
152         } catch (RuntimeException e) {
153             throw e;
154         } catch (Exception e) {
155             throw new SQLNestedException("Borrow callableStatement from pool failed", e);
156         }
157     }
158     
159     /**
160      * Create or obtain a {@link CallableStatement} from the pool.
161      * @param sql the sql string used to define the CallableStatement
162      * @param resultSetType result set type
163      * @param resultSetConcurrency result set concurrency
164      * @return a {@link PoolableCallableStatement}
165      * @throws SQLException
166      * @since 1.3
167      */
168     public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
169         try {
170             return (CallableStatement) (_pstmtPool.borrowObject(createKey(sql, resultSetType,
171                             resultSetConcurrency, STATEMENT_CALLABLESTMT)));
172         } catch (NoSuchElementException e) {
173             throw new SQLNestedException("MaxOpenCallableStatements limit reached", e);
174         } catch (RuntimeException e) {
175             throw e;
176         } catch (Exception e) {
177             throw new SQLNestedException("Borrow callableStatement from pool failed", e);
178         }
179     }
180     
181 
182 //    TODO: possible enhancement, cache these preparedStatements as well
183 
184 //    public PreparedStatement prepareStatement(String sql, int resultSetType,
185 //                                              int resultSetConcurrency,
186 //                                              int resultSetHoldability)
187 //        throws SQLException {
188 //        return super.prepareStatement(
189 //            sql, resultSetType, resultSetConcurrency, resultSetHoldability);
190 //    }
191 //
192 //    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
193 //        throws SQLException {
194 //        return super.prepareStatement(sql, autoGeneratedKeys);
195 //    }
196 //
197 //    public PreparedStatement prepareStatement(String sql, int columnIndexes[])
198 //        throws SQLException {
199 //        return super.prepareStatement(sql, columnIndexes);
200 //    }
201 //
202 //    public PreparedStatement prepareStatement(String sql, String columnNames[])
203 //        throws SQLException {
204 //        return super.prepareStatement(sql, columnNames);
205 //    }
206 
207     /**
208      * Create a PStmtKey for the given arguments.
209      * @param sql the sql string used to define the statement
210      * @param resultSetType result set type
211      * @param resultSetConcurrency result set concurrency
212      */
213     protected Object createKey(String sql, int resultSetType, int resultSetConcurrency) {
214         String catalog = null;
215         try {
216             catalog = getCatalog();
217         } catch (SQLException e) {}
218         return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency);
219     }
220     
221     /**
222      * Create a PStmtKey for the given arguments.
223      * @param sql the sql string used to define the statement
224      * @param resultSetType result set type
225      * @param resultSetConcurrency result set concurrency
226      * @param stmtType statement type - either {@link #STATEMENT_CALLABLESTMT} or {@link #STATEMENT_PREPAREDSTMT}
227      */
228     protected Object createKey(String sql, int resultSetType, int resultSetConcurrency, byte stmtType) {
229         String catalog = null;
230         try {
231             catalog = getCatalog();
232         } catch (SQLException e) {}
233         return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency, stmtType);
234     }
235 
236     /**
237      * Create a PStmtKey for the given arguments.
238      * @param sql the sql string used to define the statement
239      */
240     protected Object createKey(String sql) {
241         String catalog = null;
242         try {
243             catalog = getCatalog();
244         } catch (SQLException e) {}
245         return new PStmtKey(normalizeSQL(sql), catalog);
246     }
247     
248     /**
249      * Create a PStmtKey for the given arguments.
250      * @param sql the sql string used to define the statement
251      * @param stmtType statement type - either {@link #STATEMENT_CALLABLESTMT} or {@link #STATEMENT_PREPAREDSTMT}
252      */
253     protected Object createKey(String sql, byte stmtType) {
254         String catalog = null;
255         try {
256             catalog = getCatalog();
257         } catch (SQLException e) {}
258         return new PStmtKey(normalizeSQL(sql), catalog, stmtType);
259     }
260 
261     /**
262      * Normalize the given SQL statement, producing a
263      * cannonical form that is semantically equivalent to the original.
264      */
265     protected String normalizeSQL(String sql) {
266         return sql.trim();
267     }
268 
269     /**
270      * {@link KeyedPoolableObjectFactory} method for creating
271      * {@link PoolablePreparedStatement}s or {@link PoolableCallableStatement}s.
272      * The <code>stmtType</code> field in the key determines whether 
273      * a PoolablePreparedStatement or PoolableCallableStatement is created.
274      * 
275      * @param obj the key for the {@link PreparedStatement} to be created
276      * @see #createKey(String, int, int, byte)
277      */
278     public Object makeObject(Object obj) throws Exception {
279         if(null == obj || !(obj instanceof PStmtKey)) {
280             throw new IllegalArgumentException("Prepared statement key is null or invalid.");
281         } else {
282             PStmtKey key = (PStmtKey)obj;
283             if( null == key._resultSetType && null == key._resultSetConcurrency ) {
284                 if (key._stmtType == STATEMENT_PREPAREDSTMT ) {
285                     return new PoolablePreparedStatement(getDelegate().prepareStatement( key._sql), key, _pstmtPool, this); 
286                 } else {
287                     return new PoolableCallableStatement(getDelegate().prepareCall( key._sql), key, _pstmtPool, this);
288                 }
289             } else { // Both _resultSetType and _resultSetConcurrency are non-null here (both or neither are set by constructors)
290                 if(key._stmtType == STATEMENT_PREPAREDSTMT) {
291                     return new PoolablePreparedStatement(getDelegate().prepareStatement(
292                         key._sql, key._resultSetType.intValue(),key._resultSetConcurrency.intValue()), key, _pstmtPool, this);
293                 } else {
294                     return new PoolableCallableStatement( getDelegate().prepareCall(
295                         key._sql,key._resultSetType.intValue(), key._resultSetConcurrency.intValue()), key, _pstmtPool, this);
296                 }
297             }
298         }
299     }
300 
301     /**
302      * {@link KeyedPoolableObjectFactory} method for destroying
303      * PoolablePreparedStatements and PoolableCallableStatements.
304      * Closes the underlying statement.
305      * 
306      * @param key ignored
307      * @param obj the pooled statement to be destroyed.
308      */
309     public void destroyObject(Object key, Object obj) throws Exception {
310         if(obj instanceof DelegatingPreparedStatement) {
311             ((DelegatingPreparedStatement)obj).getInnermostDelegate().close();
312         } else {
313             ((PreparedStatement)obj).close();
314         }
315     }
316 
317     /**
318      * {@link KeyedPoolableObjectFactory} method for validating
319      * pooled statements. Currently always returns true.
320      * 
321      * @param key ignored
322      * @param obj ignored
323      * @return <tt>true</tt>
324      */
325     public boolean validateObject(Object key, Object obj) {
326         return true;
327     }
328 
329     /**
330      * {@link KeyedPoolableObjectFactory} method for activating
331      * pooled statements.
332      * 
333      * @param key ignored
334      * @param obj pooled statement to be activated
335      */
336     public void activateObject(Object key, Object obj) throws Exception {
337         ((DelegatingPreparedStatement)obj).activate();
338     }
339 
340     /**
341      * {@link KeyedPoolableObjectFactory} method for passivating
342      * {@link PreparedStatement}s or {@link CallableStatement}s.
343      * Invokes {@link PreparedStatement#clearParameters}.
344      * 
345      * @param key ignored
346      * @param obj a {@link PreparedStatement}
347      */
348     public void passivateObject(Object key, Object obj) throws Exception {
349         ((PreparedStatement)obj).clearParameters();
350         ((DelegatingPreparedStatement)obj).passivate();
351     }
352 
353     public String toString() {
354         if (_pstmtPool != null ) {
355             return "PoolingConnection: " + _pstmtPool.toString();
356         } else {
357             return "PoolingConnection: null";
358         }
359     }
360 
361     /**
362      * A key uniquely identifiying {@link PreparedStatement}s.
363      */
364     static class PStmtKey {
365         
366         /** SQL defining Prepared or Callable Statement */
367         protected String _sql = null;
368         
369         /** Result set type */
370         protected Integer _resultSetType = null;
371         
372         /** Result set concurrency */
373         protected Integer _resultSetConcurrency = null;
374         
375         /** Database catalog */
376         protected String _catalog = null;
377         
378         /** 
379          *  Statement type. Either STATEMENT_PREPAREDSTMT (PreparedStatement)
380          *  or STATEMENT_CALLABLESTMT (CallableStatement) 
381          */
382         protected byte _stmtType = STATEMENT_PREPAREDSTMT;
383         
384         PStmtKey(String sql) {
385             _sql = sql;
386         }
387 
388         PStmtKey(String sql, String catalog) {
389             _sql = sql;
390             _catalog = catalog;
391         }
392         
393         PStmtKey(String sql, String catalog, byte stmtType) {
394             _sql = sql;
395             _catalog = catalog;
396             _stmtType = stmtType;
397         }
398 
399         PStmtKey(String sql, int resultSetType, int resultSetConcurrency) {
400             _sql = sql;
401             _resultSetType = new Integer(resultSetType);
402             _resultSetConcurrency = new Integer(resultSetConcurrency);
403         }
404 
405         PStmtKey(String sql, String catalog, int resultSetType, int resultSetConcurrency) {
406             _sql = sql;
407             _catalog = catalog;
408             _resultSetType = new Integer(resultSetType);
409             _resultSetConcurrency = new Integer(resultSetConcurrency);
410         }
411         
412         PStmtKey(String sql, String catalog, int resultSetType, int resultSetConcurrency, byte stmtType) {
413             _sql = sql;
414             _catalog = catalog;
415             _resultSetType = new Integer(resultSetType);
416             _resultSetConcurrency = new Integer(resultSetConcurrency);
417             _stmtType = stmtType;
418         }
419 
420         public boolean equals(Object that) {
421             try {
422                 PStmtKey key = (PStmtKey)that;
423                 return( ((null == _sql && null == key._sql) || _sql.equals(key._sql)) &&  
424                         ((null == _catalog && null == key._catalog) || _catalog.equals(key._catalog)) &&
425                         ((null == _resultSetType && null == key._resultSetType) || _resultSetType.equals(key._resultSetType)) &&
426                         ((null == _resultSetConcurrency && null == key._resultSetConcurrency) || _resultSetConcurrency.equals(key._resultSetConcurrency)) &&
427                         (_stmtType == key._stmtType)
428                       );
429             } catch(ClassCastException e) {
430                 return false;
431             } catch(NullPointerException e) {
432                 return false;
433             }
434         }
435 
436         public int hashCode() {
437             if (_catalog==null)
438                 return(null == _sql ? 0 : _sql.hashCode());
439             else
440                 return(null == _sql ? _catalog.hashCode() : (_catalog + _sql).hashCode());
441         }
442 
443         public String toString() {
444             StringBuffer buf = new StringBuffer();
445             buf.append("PStmtKey: sql=");
446             buf.append(_sql);
447             buf.append(", catalog=");
448             buf.append(_catalog);
449             buf.append(", resultSetType=");
450             buf.append(_resultSetType);
451             buf.append(", resultSetConcurrency=");
452             buf.append(_resultSetConcurrency);
453             buf.append(", statmentType=");
454             buf.append(_stmtType);
455             return buf.toString();
456         }
457     }
458 }