001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.dbcp2; 018 019import java.sql.CallableStatement; 020import java.sql.Connection; 021import java.sql.PreparedStatement; 022import java.sql.SQLException; 023import java.sql.Statement; 024import java.util.NoSuchElementException; 025import java.util.Objects; 026 027import org.apache.commons.pool2.KeyedObjectPool; 028import org.apache.commons.pool2.KeyedPooledObjectFactory; 029import org.apache.commons.pool2.PooledObject; 030import org.apache.commons.pool2.impl.DefaultPooledObject; 031import org.apache.commons.pool2.impl.GenericKeyedObjectPool; 032 033/** 034 * A {@link DelegatingConnection} that pools {@link PreparedStatement}s. 035 * <p> 036 * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than creating a new PreparedStatement each 037 * time, may actually pull the statement from a pool of unused statements. The {@link PreparedStatement#close} method of 038 * the returned statement doesn't actually close the statement, but rather returns it to the pool. (See 039 * {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.) 040 * </p> 041 * 042 * @see PoolablePreparedStatement 043 * @since 2.0 044 */ 045public class PoolingConnection extends DelegatingConnection<Connection> 046 implements KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> { 047 048 /** 049 * Statement types. 050 * 051 * See subclasses of {@link Statement}. 052 * 053 * @since 2.0 protected enum. 054 * @since 2.4.0 public enum. 055 * @see Statement 056 * @see CallableStatement 057 * @see PreparedStatement 058 */ 059 public enum StatementType { 060 061 /** 062 * Callable statement. 063 * 064 * @see CallableStatement 065 */ 066 CALLABLE_STATEMENT, 067 068 /** 069 * Prepared statement. 070 * 071 * @see PreparedStatement 072 */ 073 PREPARED_STATEMENT 074 } 075 076 /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */ 077 private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pStmtPool; 078 079 private boolean clearStatementPoolOnReturn; 080 081 /** 082 * Constructs a new instance. 083 * 084 * @param connection 085 * the underlying {@link Connection}. 086 */ 087 public PoolingConnection(final Connection connection) { 088 super(connection); 089 } 090 091 /** 092 * {@link KeyedPooledObjectFactory} method for activating pooled statements. 093 * 094 * @param key 095 * ignored 096 * @param pooledObject 097 * wrapped pooled statement to be activated 098 */ 099 @Override 100 public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) 101 throws SQLException { 102 pooledObject.getObject().activate(); 103 } 104 105 /** 106 * Closes and frees all {@link PreparedStatement}s or {@link CallableStatement}s from the pool, and close the 107 * underlying connection. 108 */ 109 @Override 110 public synchronized void close() throws SQLException { 111 try { 112 if (null != pStmtPool) { 113 final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> oldPool = pStmtPool; 114 pStmtPool = null; 115 try { 116 oldPool.close(); 117 } catch (final RuntimeException e) { 118 throw e; 119 } catch (final Exception e) { 120 throw new SQLException("Cannot close connection", e); 121 } 122 } 123 } finally { 124 try { 125 @SuppressWarnings("resource") 126 final Connection delegateInternal = getDelegateInternal(); 127 if (delegateInternal != null) { 128 delegateInternal.close(); 129 } 130 } finally { 131 setClosedInternal(true); 132 } 133 } 134 } 135 136 /** 137 * Notification from {@link PoolableConnection} that we returned to the pool. 138 * 139 * @throws SQLException when {@code clearStatementPoolOnReturn} is true and the statement pool could not be 140 * cleared 141 * @since 2.8.0 142 */ 143 public void connectionReturnedToPool() throws SQLException { 144 if (pStmtPool != null && clearStatementPoolOnReturn) { 145 try { 146 pStmtPool.clear(); 147 } catch (final Exception e) { 148 throw new SQLException("Error clearing statement pool", e); 149 } 150 } 151 } 152 153 /** 154 * Creates a PStmtKey for the given arguments. 155 * 156 * @param sql 157 * the SQL string used to define the statement 158 * 159 * @return the PStmtKey created for the given arguments. 160 */ 161 protected PStmtKey createKey(final String sql) { 162 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull()); 163 } 164 165 /** 166 * Creates a PStmtKey for the given arguments. 167 * 168 * @param sql 169 * the SQL string used to define the statement 170 * @param autoGeneratedKeys 171 * A flag indicating whether auto-generated keys should be returned; one of 172 * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. 173 * 174 * @return the PStmtKey created for the given arguments. 175 */ 176 protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) { 177 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys); 178 } 179 180 /** 181 * Creates a PStmtKey for the given arguments. 182 * 183 * @param sql 184 * the SQL string used to define the statement 185 * @param resultSetType 186 * result set type 187 * @param resultSetConcurrency 188 * result set concurrency 189 * 190 * @return the PStmtKey created for the given arguments. 191 */ 192 protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) { 193 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency); 194 } 195 196 /** 197 * Creates a PStmtKey for the given arguments. 198 * 199 * @param sql 200 * the SQL string used to define the statement 201 * @param resultSetType 202 * result set type 203 * @param resultSetConcurrency 204 * result set concurrency 205 * @param resultSetHoldability 206 * result set holdability 207 * 208 * @return the PStmtKey created for the given arguments. 209 */ 210 protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, 211 final int resultSetHoldability) { 212 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, 213 resultSetHoldability); 214 } 215 216 /** 217 * Creates a PStmtKey for the given arguments. 218 * 219 * @param sql 220 * the SQL string used to define the statement 221 * @param resultSetType 222 * result set type 223 * @param resultSetConcurrency 224 * result set concurrency 225 * @param resultSetHoldability 226 * result set holdability 227 * @param statementType 228 * statement type 229 * 230 * @return the PStmtKey created for the given arguments. 231 */ 232 protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, 233 final int resultSetHoldability, final StatementType statementType) { 234 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, 235 resultSetHoldability, statementType); 236 } 237 238 /** 239 * Creates a PStmtKey for the given arguments. 240 * 241 * @param sql 242 * the SQL string used to define the statement 243 * @param resultSetType 244 * result set type 245 * @param resultSetConcurrency 246 * result set concurrency 247 * @param statementType 248 * statement type 249 * 250 * @return the PStmtKey created for the given arguments. 251 */ 252 protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, 253 final StatementType statementType) { 254 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType); 255 } 256 257 /** 258 * Creates a PStmtKey for the given arguments. 259 * 260 * @param sql 261 * the SQL string used to define the statement 262 * @param columnIndexes 263 * An array of column indexes indicating the columns that should be returned from the inserted row or 264 * rows. 265 * 266 * @return the PStmtKey created for the given arguments. 267 */ 268 protected PStmtKey createKey(final String sql, final int[] columnIndexes) { 269 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes); 270 } 271 272 /** 273 * Creates a PStmtKey for the given arguments. 274 * 275 * @param sql 276 * the SQL string used to define the statement 277 * @param statementType 278 * statement type 279 * 280 * @return the PStmtKey created for the given arguments. 281 */ 282 protected PStmtKey createKey(final String sql, final StatementType statementType) { 283 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType, null); 284 } 285 286 /** 287 * Creates a PStmtKey for the given arguments. 288 * 289 * @param sql 290 * the SQL string used to define the statement 291 * @param columnNames 292 * column names 293 * 294 * @return the PStmtKey created for the given arguments. 295 */ 296 protected PStmtKey createKey(final String sql, final String[] columnNames) { 297 return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames); 298 } 299 300 /** 301 * {@link KeyedPooledObjectFactory} method for destroying PoolablePreparedStatements and PoolableCallableStatements. 302 * Closes the underlying statement. 303 * 304 * @param key 305 * ignored 306 * @param pooledObject 307 * the wrapped pooled statement to be destroyed. 308 */ 309 @Override 310 public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) throws SQLException { 311 if (pooledObject != null) { 312 @SuppressWarnings("resource") 313 final DelegatingPreparedStatement object = pooledObject.getObject(); 314 if (object != null) { 315 @SuppressWarnings("resource") 316 final Statement innermostDelegate = object.getInnermostDelegate(); 317 if (innermostDelegate != null) { 318 innermostDelegate.close(); 319 } 320 } 321 } 322 } 323 324 private String getCatalogOrNull() { 325 try { 326 return getCatalog(); 327 } catch (final SQLException ignored) { 328 return null; 329 } 330 } 331 332 private String getSchemaOrNull() { 333 try { 334 return getSchema(); 335 } catch (final SQLException ignored) { 336 return null; 337 } 338 } 339 340 /** 341 * Gets the prepared statement pool. 342 * 343 * @return statement pool 344 * @since 2.8.0 345 */ 346 public KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> getStatementPool() { 347 return pStmtPool; 348 } 349 350 /** 351 * {@link KeyedPooledObjectFactory} method for creating {@link PoolablePreparedStatement}s or 352 * {@link PoolableCallableStatement}s. The {@code stmtType} field in the key determines whether a 353 * PoolablePreparedStatement or PoolableCallableStatement is created. 354 * 355 * @param key 356 * the key for the {@link PreparedStatement} to be created 357 * @see #createKey(String, int, int, StatementType) 358 */ 359 @SuppressWarnings("resource") 360 @Override 361 public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws SQLException { 362 if (null == key) { 363 throw new IllegalArgumentException("Prepared statement key is null or invalid."); 364 } 365 if (key.getStmtType() == StatementType.PREPARED_STATEMENT) { 366 final PreparedStatement statement = (PreparedStatement) key.createStatement(getDelegate()); 367 @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this 368 final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool, this); 369 return new DefaultPooledObject<>(pps); 370 } 371 final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate()); 372 final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool, this); 373 return new DefaultPooledObject<>(pcs); 374 } 375 376 /** 377 * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original. 378 * 379 * @param sql The statement to be normalized. 380 * 381 * @return The canonical form of the supplied SQL statement. 382 */ 383 protected String normalizeSQL(final String sql) { 384 return sql.trim(); 385 } 386 387 /** 388 * {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s or {@link CallableStatement}s. 389 * Invokes {@link PreparedStatement#clearParameters}. 390 * 391 * @param key 392 * ignored 393 * @param pooledObject 394 * a wrapped {@link PreparedStatement} 395 */ 396 @Override 397 public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) 398 throws SQLException { 399 @SuppressWarnings("resource") 400 final DelegatingPreparedStatement dps = pooledObject.getObject(); 401 dps.clearParameters(); 402 dps.passivate(); 403 } 404 405 /** 406 * Creates or obtains a {@link CallableStatement} from the pool. 407 * 408 * @param key 409 * a {@link PStmtKey} for the given arguments 410 * @return a {@link PoolableCallableStatement} 411 * @throws SQLException 412 * Wraps an underlying exception. 413 */ 414 private CallableStatement prepareCall(final PStmtKey key) throws SQLException { 415 return (CallableStatement) prepareStatement(key); 416 } 417 418 /** 419 * Creates or obtains a {@link CallableStatement} from the pool. 420 * 421 * @param sql 422 * the SQL string used to define the CallableStatement 423 * @return a {@link PoolableCallableStatement} 424 * @throws SQLException 425 * Wraps an underlying exception. 426 */ 427 @Override 428 public CallableStatement prepareCall(final String sql) throws SQLException { 429 return prepareCall(createKey(sql, StatementType.CALLABLE_STATEMENT)); 430 } 431 432 /** 433 * Creates or obtains a {@link CallableStatement} from the pool. 434 * 435 * @param sql 436 * the SQL string used to define the CallableStatement 437 * @param resultSetType 438 * result set type 439 * @param resultSetConcurrency 440 * result set concurrency 441 * @return a {@link PoolableCallableStatement} 442 * @throws SQLException 443 * Wraps an underlying exception. 444 */ 445 @Override 446 public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) 447 throws SQLException { 448 return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); 449 } 450 451 /** 452 * Creates or obtains a {@link CallableStatement} from the pool. 453 * 454 * @param sql 455 * the SQL string used to define the CallableStatement 456 * @param resultSetType 457 * result set type 458 * @param resultSetConcurrency 459 * result set concurrency 460 * @param resultSetHoldability 461 * result set holdability 462 * @return a {@link PoolableCallableStatement} 463 * @throws SQLException 464 * Wraps an underlying exception. 465 */ 466 @Override 467 public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, 468 final int resultSetHoldability) throws SQLException { 469 return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, 470 resultSetHoldability, StatementType.CALLABLE_STATEMENT)); 471 } 472 473 /** 474 * Creates or obtains a {@link PreparedStatement} from the pool. 475 * 476 * @param key 477 * a {@link PStmtKey} for the given arguments 478 * @return a {@link PoolablePreparedStatement} 479 * @throws SQLException 480 * Wraps an underlying exception. 481 */ 482 private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException { 483 if (null == pStmtPool) { 484 throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); 485 } 486 try { 487 return pStmtPool.borrowObject(key); 488 } catch (final NoSuchElementException e) { 489 throw new SQLException("MaxOpenPreparedStatements limit reached", e); 490 } catch (final RuntimeException e) { 491 throw e; 492 } catch (final Exception e) { 493 throw new SQLException("Borrow prepareStatement from pool failed", e); 494 } 495 } 496 497 /** 498 * Creates or obtains a {@link PreparedStatement} from the pool. 499 * 500 * @param sql 501 * the SQL string used to define the PreparedStatement 502 * @return a {@link PoolablePreparedStatement} 503 * @throws SQLException 504 * Wraps an underlying exception. 505 */ 506 @Override 507 public PreparedStatement prepareStatement(final String sql) throws SQLException { 508 return prepareStatement(createKey(sql)); 509 } 510 511 /* 512 * Creates or obtains a {@link PreparedStatement} from the pool. 513 * 514 * @param sql 515 * the SQL string used to define the PreparedStatement 516 * @param autoGeneratedKeys 517 * A flag indicating whether auto-generated keys should be returned; one of 518 * {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}. 519 * @return a {@link PoolablePreparedStatement} 520 * @throws SQLException 521 * Wraps an underlying exception. 522 */ 523 @Override 524 public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { 525 return prepareStatement(createKey(sql, autoGeneratedKeys)); 526 } 527 528 /** 529 * Creates or obtains a {@link PreparedStatement} from the pool. 530 * 531 * @param sql 532 * the SQL string used to define the PreparedStatement 533 * @param resultSetType 534 * result set type 535 * @param resultSetConcurrency 536 * result set concurrency 537 * @return a {@link PoolablePreparedStatement} 538 * @throws SQLException 539 * Wraps an underlying exception. 540 */ 541 @Override 542 public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) 543 throws SQLException { 544 return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency)); 545 } 546 547 /** 548 * Creates or obtains a {@link PreparedStatement} from the pool. 549 * 550 * @param sql 551 * the SQL string used to define the PreparedStatement 552 * @param resultSetType 553 * result set type 554 * @param resultSetConcurrency 555 * result set concurrency 556 * @param resultSetHoldability 557 * result set holdability 558 * @return a {@link PoolablePreparedStatement} 559 * @throws SQLException 560 * Wraps an underlying exception. 561 */ 562 @Override 563 public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, 564 final int resultSetHoldability) throws SQLException { 565 return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); 566 } 567 568 /** 569 * Creates or obtains a {@link PreparedStatement} from the pool. 570 * 571 * @param sql 572 * the SQL string used to define the PreparedStatement 573 * @param columnIndexes 574 * An array of column indexes indicating the columns that should be returned from the inserted row or 575 * rows. 576 * @return a {@link PoolablePreparedStatement} 577 * @throws SQLException 578 * Wraps an underlying exception. 579 */ 580 @Override 581 public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { 582 return prepareStatement(createKey(sql, columnIndexes)); 583 } 584 585 /** 586 * Creates or obtains a {@link PreparedStatement} from the pool. 587 * 588 * @param sql 589 * the SQL string used to define the PreparedStatement 590 * @param columnNames 591 * column names 592 * @return a {@link PoolablePreparedStatement} 593 * @throws SQLException 594 * Wraps an underlying exception. 595 */ 596 @Override 597 public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { 598 return prepareStatement(createKey(sql, columnNames)); 599 } 600 601 /** 602 * Sets whether the pool of statements should be cleared when the connection is returned to its pool. 603 * Default is false. 604 * 605 * @param clearStatementPoolOnReturn clear or not 606 * @since 2.8.0 607 */ 608 public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { 609 this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; 610 } 611 612 /** 613 * Sets the prepared statement pool. 614 * 615 * @param pool 616 * the prepared statement pool. 617 */ 618 public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pool) { 619 pStmtPool = pool; 620 } 621 622 @Override 623 public synchronized String toString() { 624 if (pStmtPool instanceof GenericKeyedObjectPool) { 625 // DBCP-596 PoolingConnection.toString() causes StackOverflowError 626 final GenericKeyedObjectPool<?, ?> gkop = (GenericKeyedObjectPool<?, ?>) pStmtPool; 627 if (gkop.getFactory() == this) { 628 return "PoolingConnection: " + pStmtPool.getClass() + "@" + System.identityHashCode(pStmtPool); 629 } 630 } 631 return "PoolingConnection: " + Objects.toString(pStmtPool); 632 } 633 634 /** 635 * {@link KeyedPooledObjectFactory} method for validating pooled statements. Currently, always returns true. 636 * 637 * @param key 638 * ignored 639 * @param pooledObject 640 * ignored 641 * @return {@code true} 642 */ 643 @Override 644 public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) { 645 return true; 646 } 647}