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 * https://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> stmtPool; 078 079 private volatile 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 != stmtPool) { 113 final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> oldPool = stmtPool; 114 stmtPool = 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 (stmtPool != null && clearStatementPoolOnReturn) { 145 try { 146 stmtPool.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 * {@link Statement#RETURN_GENERATED_KEYS} or {@link 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 stmtPool; 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, stmtPool, this); 369 return new DefaultPooledObject<>(pps); 370 } 371 final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate()); 372 final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, stmtPool, 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 * @return The canonical form of the supplied SQL statement. 381 */ 382 protected String normalizeSQL(final String sql) { 383 return sql.trim(); 384 } 385 386 /** 387 * {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s or {@link CallableStatement}s. 388 * Invokes {@link PreparedStatement#clearParameters}. 389 * 390 * @param key 391 * ignored 392 * @param pooledObject 393 * a wrapped {@link PreparedStatement} 394 */ 395 @Override 396 public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) 397 throws SQLException { 398 @SuppressWarnings("resource") 399 final DelegatingPreparedStatement dps = pooledObject.getObject(); 400 dps.clearParameters(); 401 dps.passivate(); 402 } 403 404 /** 405 * Creates or obtains a {@link CallableStatement} from the pool. 406 * 407 * @param key 408 * a {@link PStmtKey} for the given arguments 409 * @return a {@link PoolableCallableStatement} 410 * @throws SQLException 411 * Wraps an underlying exception. 412 */ 413 private CallableStatement prepareCall(final PStmtKey key) throws SQLException { 414 return (CallableStatement) prepareStatement(key); 415 } 416 417 /** 418 * Creates or obtains a {@link CallableStatement} from the pool. 419 * 420 * @param sql 421 * the SQL string used to define the CallableStatement 422 * @return a {@link PoolableCallableStatement} 423 * @throws SQLException 424 * Wraps an underlying exception. 425 */ 426 @Override 427 public CallableStatement prepareCall(final String sql) throws SQLException { 428 return prepareCall(createKey(sql, StatementType.CALLABLE_STATEMENT)); 429 } 430 431 /** 432 * Creates or obtains a {@link CallableStatement} from the pool. 433 * 434 * @param sql 435 * the SQL string used to define the CallableStatement 436 * @param resultSetType 437 * result set type 438 * @param resultSetConcurrency 439 * result set concurrency 440 * @return a {@link PoolableCallableStatement} 441 * @throws SQLException 442 * Wraps an underlying exception. 443 */ 444 @Override 445 public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) 446 throws SQLException { 447 return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT)); 448 } 449 450 /** 451 * Creates or obtains a {@link CallableStatement} from the pool. 452 * 453 * @param sql 454 * the SQL string used to define the CallableStatement 455 * @param resultSetType 456 * result set type 457 * @param resultSetConcurrency 458 * result set concurrency 459 * @param resultSetHoldability 460 * result set holdability 461 * @return a {@link PoolableCallableStatement} 462 * @throws SQLException 463 * Wraps an underlying exception. 464 */ 465 @Override 466 public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, 467 final int resultSetHoldability) throws SQLException { 468 return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, 469 resultSetHoldability, StatementType.CALLABLE_STATEMENT)); 470 } 471 472 /** 473 * Creates or obtains a {@link PreparedStatement} from the pool. 474 * 475 * @param key 476 * a {@link PStmtKey} for the given arguments 477 * @return a {@link PoolablePreparedStatement} 478 * @throws SQLException 479 * Wraps an underlying exception. 480 */ 481 private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException { 482 if (null == stmtPool) { 483 throw new SQLException("Statement pool is null - closed or invalid PoolingConnection."); 484 } 485 try { 486 return stmtPool.borrowObject(key); 487 } catch (final NoSuchElementException e) { 488 throw new SQLException("MaxOpenPreparedStatements limit reached", e); 489 } catch (final RuntimeException e) { 490 throw e; 491 } catch (final Exception e) { 492 throw new SQLException("Borrow prepareStatement from pool failed", e); 493 } 494 } 495 496 /** 497 * Creates or obtains a {@link PreparedStatement} from the pool. 498 * 499 * @param sql 500 * the SQL string used to define the PreparedStatement 501 * @return a {@link PoolablePreparedStatement} 502 * @throws SQLException 503 * Wraps an underlying exception. 504 */ 505 @Override 506 public PreparedStatement prepareStatement(final String sql) throws SQLException { 507 return prepareStatement(createKey(sql)); 508 } 509 510 /* 511 * Creates or obtains a {@link PreparedStatement} from the pool. 512 * 513 * @param sql 514 * the SQL string used to define the PreparedStatement 515 * @param autoGeneratedKeys 516 * A flag indicating whether auto-generated keys should be returned; one of 517 * {@link Statement#RETURN_GENERATED_KEYS} or {@link Statement#NO_GENERATED_KEYS}. 518 * @return a {@link PoolablePreparedStatement} 519 * @throws SQLException 520 * Wraps an underlying exception. 521 */ 522 @Override 523 public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { 524 return prepareStatement(createKey(sql, autoGeneratedKeys)); 525 } 526 527 /** 528 * Creates or obtains a {@link PreparedStatement} from the pool. 529 * 530 * @param sql 531 * the SQL string used to define the PreparedStatement 532 * @param resultSetType 533 * result set type 534 * @param resultSetConcurrency 535 * result set concurrency 536 * @return a {@link PoolablePreparedStatement} 537 * @throws SQLException 538 * Wraps an underlying exception. 539 */ 540 @Override 541 public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) 542 throws SQLException { 543 return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency)); 544 } 545 546 /** 547 * Creates or obtains a {@link PreparedStatement} from the pool. 548 * 549 * @param sql 550 * the SQL string used to define the PreparedStatement 551 * @param resultSetType 552 * result set type 553 * @param resultSetConcurrency 554 * result set concurrency 555 * @param resultSetHoldability 556 * result set holdability 557 * @return a {@link PoolablePreparedStatement} 558 * @throws SQLException 559 * Wraps an underlying exception. 560 */ 561 @Override 562 public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, 563 final int resultSetHoldability) throws SQLException { 564 return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); 565 } 566 567 /** 568 * Creates or obtains a {@link PreparedStatement} from the pool. 569 * 570 * @param sql 571 * the SQL string used to define the PreparedStatement 572 * @param columnIndexes 573 * An array of column indexes indicating the columns that should be returned from the inserted row or 574 * rows. 575 * @return a {@link PoolablePreparedStatement} 576 * @throws SQLException 577 * Wraps an underlying exception. 578 */ 579 @Override 580 public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { 581 return prepareStatement(createKey(sql, columnIndexes)); 582 } 583 584 /** 585 * Creates or obtains a {@link PreparedStatement} from the pool. 586 * 587 * @param sql 588 * the SQL string used to define the PreparedStatement 589 * @param columnNames 590 * column names 591 * @return a {@link PoolablePreparedStatement} 592 * @throws SQLException 593 * Wraps an underlying exception. 594 */ 595 @Override 596 public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { 597 return prepareStatement(createKey(sql, columnNames)); 598 } 599 600 /** 601 * Sets whether the pool of statements should be cleared when the connection is returned to its pool. 602 * Default is false. 603 * 604 * @param clearStatementPoolOnReturn clear or not 605 * @since 2.8.0 606 */ 607 public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { 608 this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; 609 } 610 611 /** 612 * Sets the prepared statement pool. 613 * 614 * @param pool 615 * the prepared statement pool. 616 */ 617 public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pool) { 618 stmtPool = pool; 619 } 620 621 @Override 622 public synchronized String toString() { 623 if (stmtPool instanceof GenericKeyedObjectPool) { 624 // DBCP-596 PoolingConnection.toString() causes StackOverflowError 625 final GenericKeyedObjectPool<?, ?> gkop = (GenericKeyedObjectPool<?, ?>) stmtPool; 626 if (gkop.getFactory() == this) { 627 return "PoolingConnection: " + stmtPool.getClass() + "@" + System.identityHashCode(stmtPool); 628 } 629 } 630 return "PoolingConnection: " + Objects.toString(stmtPool); 631 } 632 633 /** 634 * {@link KeyedPooledObjectFactory} method for validating pooled statements. Currently, always returns true. 635 * 636 * @param key 637 * ignored 638 * @param pooledObject 639 * ignored 640 * @return {@code true} 641 */ 642 @Override 643 public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) { 644 return true; 645 } 646}