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 */ 017 018package org.apache.commons.dbcp2; 019 020import java.sql.Connection; 021import java.sql.SQLException; 022import java.sql.Statement; 023import java.util.Collection; 024import java.util.Objects; 025import java.util.concurrent.atomic.AtomicLong; 026 027import javax.management.ObjectName; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.apache.commons.pool2.DestroyMode; 032import org.apache.commons.pool2.KeyedObjectPool; 033import org.apache.commons.pool2.ObjectPool; 034import org.apache.commons.pool2.PooledObject; 035import org.apache.commons.pool2.PooledObjectFactory; 036import org.apache.commons.pool2.impl.DefaultPooledObject; 037import org.apache.commons.pool2.impl.GenericKeyedObjectPool; 038import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; 039 040/** 041 * A {@link PooledObjectFactory} that creates {@link PoolableConnection}s. 042 * 043 * @since 2.0 044 */ 045public class PoolableConnectionFactory implements PooledObjectFactory<PoolableConnection> { 046 047 private static final Log log = LogFactory.getLog(PoolableConnectionFactory.class); 048 049 /** 050 * Internal constant to indicate the level is not set. 051 */ 052 static final int UNKNOWN_TRANSACTION_ISOLATION = -1; 053 054 private final ConnectionFactory connectionFactory; 055 056 private final ObjectName dataSourceJmxObjectName; 057 058 private volatile String validationQuery; 059 060 private volatile int validationQueryTimeoutSeconds = -1; 061 062 private Collection<String> connectionInitSqls; 063 064 private Collection<String> disconnectionSqlCodes; 065 066 private boolean fastFailValidation = true; 067 068 private volatile ObjectPool<PoolableConnection> pool; 069 070 private Boolean defaultReadOnly; 071 072 private Boolean defaultAutoCommit; 073 074 private boolean autoCommitOnReturn = true; 075 076 private boolean rollbackOnReturn = true; 077 078 private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION; 079 080 private String defaultCatalog; 081 082 private String defaultSchema; 083 084 private boolean cacheState; 085 086 private boolean poolStatements; 087 088 private boolean clearStatementPoolOnReturn; 089 090 private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; 091 092 private long maxConnLifetimeMillis = -1; 093 094 private final AtomicLong connectionIndex = new AtomicLong(); 095 096 private Integer defaultQueryTimeoutSeconds; 097 098 /** 099 * Creates a new {@code PoolableConnectionFactory}. 100 * 101 * @param connFactory 102 * the {@link ConnectionFactory} from which to obtain base {@link Connection}s 103 * @param dataSourceJmxObjectName 104 * The JMX object name, may be null. 105 */ 106 public PoolableConnectionFactory(final ConnectionFactory connFactory, final ObjectName dataSourceJmxObjectName) { 107 this.connectionFactory = connFactory; 108 this.dataSourceJmxObjectName = dataSourceJmxObjectName; 109 } 110 111 @Override 112 public void activateObject(final PooledObject<PoolableConnection> p) throws Exception { 113 114 validateLifetime(p); 115 116 final PoolableConnection pConnection = p.getObject(); 117 pConnection.activate(); 118 119 if (defaultAutoCommit != null && pConnection.getAutoCommit() != defaultAutoCommit) { 120 pConnection.setAutoCommit(defaultAutoCommit); 121 } 122 if (defaultTransactionIsolation != UNKNOWN_TRANSACTION_ISOLATION 123 && pConnection.getTransactionIsolation() != defaultTransactionIsolation) { 124 pConnection.setTransactionIsolation(defaultTransactionIsolation); 125 } 126 if (defaultReadOnly != null && pConnection.isReadOnly() != defaultReadOnly) { 127 pConnection.setReadOnly(defaultReadOnly); 128 } 129 if (defaultCatalog != null && !defaultCatalog.equals(pConnection.getCatalog())) { 130 pConnection.setCatalog(defaultCatalog); 131 } 132 if (defaultSchema != null && !defaultSchema.equals(Jdbc41Bridge.getSchema(pConnection))) { 133 Jdbc41Bridge.setSchema(pConnection, defaultSchema); 134 } 135 pConnection.setDefaultQueryTimeout(defaultQueryTimeoutSeconds); 136 } 137 138 @Override 139 public void destroyObject(final PooledObject<PoolableConnection> p) throws Exception { 140 p.getObject().reallyClose(); 141 } 142 143 /** 144 * @since 2.9.0 145 */ 146 @Override 147 public void destroyObject(final PooledObject<PoolableConnection> p, final DestroyMode mode) throws Exception { 148 if (mode == DestroyMode.ABANDONED) { 149 p.getObject().getInnermostDelegate().abort(Runnable::run); 150 } else { 151 p.getObject().reallyClose(); 152 } 153 } 154 155 /** 156 * Gets the cache state. 157 * 158 * @return The cache state. 159 * @since Made public in 2.6.0. 160 */ 161 public boolean getCacheState() { 162 return cacheState; 163 } 164 165 /** 166 * Gets the connection factory. 167 * 168 * @return The connection factory. 169 * @since Made public in 2.6.0. 170 */ 171 public ConnectionFactory getConnectionFactory() { 172 return connectionFactory; 173 } 174 175 protected AtomicLong getConnectionIndex() { 176 return connectionIndex; 177 } 178 179 /** 180 * @return The collection of initialization SQL statements. 181 * @since 2.6.0 182 */ 183 public Collection<String> getConnectionInitSqls() { 184 return connectionInitSqls; 185 } 186 187 /** 188 * @return The data source JMX ObjectName 189 * @since Made public in 2.6.0. 190 */ 191 public ObjectName getDataSourceJmxName() { 192 return dataSourceJmxObjectName; 193 } 194 195 /** 196 * @return The data source JMS ObjectName. 197 * @since 2.6.0 198 */ 199 public ObjectName getDataSourceJmxObjectName() { 200 return dataSourceJmxObjectName; 201 } 202 203 /** 204 * @return Default auto-commit value. 205 * @since 2.6.0 206 */ 207 public Boolean getDefaultAutoCommit() { 208 return defaultAutoCommit; 209 } 210 211 /** 212 * @return Default catalog. 213 * @since 2.6.0 214 */ 215 public String getDefaultCatalog() { 216 return defaultCatalog; 217 } 218 219 /** 220 * @return Default query timeout in seconds. 221 */ 222 public Integer getDefaultQueryTimeout() { 223 return defaultQueryTimeoutSeconds; 224 } 225 226 /** 227 * @return Default query timeout in seconds. 228 * @since 2.6.0 229 */ 230 public Integer getDefaultQueryTimeoutSeconds() { 231 return defaultQueryTimeoutSeconds; 232 } 233 234 /** 235 * @return Default read-only-value. 236 * @since 2.6.0 237 */ 238 public Boolean getDefaultReadOnly() { 239 return defaultReadOnly; 240 } 241 242 /** 243 * @return Default schema. 244 * @since 2.6.0 245 */ 246 public String getDefaultSchema() { 247 return defaultSchema; 248 } 249 250 /** 251 * @return Default transaction isolation. 252 * @since 2.6.0 253 */ 254 public int getDefaultTransactionIsolation() { 255 return defaultTransactionIsolation; 256 } 257 258 /** 259 * SQL_STATE codes considered to signal fatal conditions. 260 * <p> 261 * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with 262 * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is 263 * {@code true}, whenever connections created by this factory generate exceptions with SQL_STATE codes in this list, 264 * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or 265 * validation query). 266 * </p> 267 * <p> 268 * If {@link #isFastFailValidation()} is {@code false} setting this property has no effect. 269 * </p> 270 * 271 * @return SQL_STATE codes overriding defaults 272 * @since 2.1 273 */ 274 public Collection<String> getDisconnectionSqlCodes() { 275 return disconnectionSqlCodes; 276 } 277 278 /** 279 * @return Maximum connection lifetime in milliseconds. 280 * @since 2.6.0 281 */ 282 public long getMaxConnLifetimeMillis() { 283 return maxConnLifetimeMillis; 284 } 285 286 protected int getMaxOpenPreparedStatements() { 287 return maxOpenPreparedStatements; 288 } 289 /** 290 * Returns the {@link ObjectPool} in which {@link Connection}s are pooled. 291 * 292 * @return the connection pool 293 */ 294 public synchronized ObjectPool<PoolableConnection> getPool() { 295 return pool; 296 } 297 /** 298 * @return Whether to pool statements. 299 * @since Made public in 2.6.0. 300 */ 301 public boolean getPoolStatements() { 302 return poolStatements; 303 } 304 /** 305 * @return Validation query. 306 * @since 2.6.0 307 */ 308 public String getValidationQuery() { 309 return validationQuery; 310 } 311 312 /** 313 * @return Validation query timeout in seconds. 314 * @since 2.6.0 315 */ 316 public int getValidationQueryTimeoutSeconds() { 317 return validationQueryTimeoutSeconds; 318 } 319 320 protected void initializeConnection(final Connection conn) throws SQLException { 321 final Collection<String> sqls = connectionInitSqls; 322 if (conn.isClosed()) { 323 throw new SQLException("initializeConnection: connection closed"); 324 } 325 if (null != sqls) { 326 try (Statement stmt = conn.createStatement()) { 327 for (final String sql : sqls) { 328 Objects.requireNonNull(sql, "null connectionInitSqls element"); 329 stmt.execute(sql); 330 } 331 } 332 } 333 } 334 335 /** 336 * @return Whether to auto-commit on return. 337 * @since 2.6.0 338 */ 339 public boolean isAutoCommitOnReturn() { 340 return autoCommitOnReturn; 341 } 342 343 /** 344 * @return Whether to auto-commit on return. 345 * @deprecated Use {@link #isAutoCommitOnReturn()}. 346 */ 347 @Deprecated 348 public boolean isEnableAutoCommitOnReturn() { 349 return autoCommitOnReturn; 350 } 351 352 /** 353 * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with 354 * SQL_STATE indicating fatal disconnection errors. 355 * 356 * @return true if connections created by this factory will fast fail validation. 357 * @see #setDisconnectionSqlCodes(Collection) 358 * @since 2.1 359 * @since 2.5.0 Defaults to true, previous versions defaulted to false. 360 */ 361 public boolean isFastFailValidation() { 362 return fastFailValidation; 363 } 364 365 /** 366 * @return Whether to rollback on return. 367 */ 368 public boolean isRollbackOnReturn() { 369 return rollbackOnReturn; 370 } 371 372 @Override 373 public PooledObject<PoolableConnection> makeObject() throws Exception { 374 Connection conn = connectionFactory.createConnection(); 375 if (conn == null) { 376 throw new IllegalStateException("Connection factory returned null from createConnection"); 377 } 378 try { 379 initializeConnection(conn); 380 } catch (final SQLException sqle) { 381 // Make sure the connection is closed 382 Utils.closeQuietly(conn); 383 // Rethrow original exception so it is visible to caller 384 throw sqle; 385 } 386 387 final long connIndex = connectionIndex.getAndIncrement(); 388 389 if (poolStatements) { 390 conn = new PoolingConnection(conn); 391 final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>(); 392 config.setMaxTotalPerKey(-1); 393 config.setBlockWhenExhausted(false); 394 config.setMaxWaitMillis(0); 395 config.setMaxIdlePerKey(1); 396 config.setMaxTotal(maxOpenPreparedStatements); 397 if (dataSourceJmxObjectName != null) { 398 final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString()); 399 base.append(Constants.JMX_CONNECTION_BASE_EXT); 400 base.append(connIndex); 401 config.setJmxNameBase(base.toString()); 402 config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); 403 } else { 404 config.setJmxEnabled(false); 405 } 406 final PoolingConnection poolingConn = (PoolingConnection) conn; 407 final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>( 408 poolingConn, config); 409 poolingConn.setStatementPool(stmtPool); 410 poolingConn.setClearStatementPoolOnReturn(clearStatementPoolOnReturn); 411 poolingConn.setCacheState(cacheState); 412 } 413 414 // Register this connection with JMX 415 final ObjectName connJmxName; 416 if (dataSourceJmxObjectName == null) { 417 connJmxName = null; 418 } else { 419 connJmxName = new ObjectName( 420 dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex); 421 } 422 423 final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes, 424 fastFailValidation); 425 pc.setCacheState(cacheState); 426 427 return new DefaultPooledObject<>(pc); 428 } 429 430 @Override 431 public void passivateObject(final PooledObject<PoolableConnection> p) throws Exception { 432 433 validateLifetime(p); 434 435 final PoolableConnection conn = p.getObject(); 436 Boolean connAutoCommit = null; 437 if (rollbackOnReturn) { 438 connAutoCommit = conn.getAutoCommit(); 439 if (!connAutoCommit && !conn.isReadOnly()) { 440 conn.rollback(); 441 } 442 } 443 444 conn.clearWarnings(); 445 446 // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should 447 // have autoCommit enabled 448 if (autoCommitOnReturn) { 449 if (connAutoCommit == null) { 450 connAutoCommit = conn.getAutoCommit(); 451 } 452 if (!connAutoCommit) { 453 conn.setAutoCommit(true); 454 } 455 } 456 457 conn.passivate(); 458 } 459 460 public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) { 461 this.autoCommitOnReturn = autoCommitOnReturn; 462 } 463 464 public void setCacheState(final boolean cacheState) { 465 this.cacheState = cacheState; 466 } 467 468 /** 469 * Sets whether the pool of statements (which was enabled with {@link #setPoolStatements(boolean)}) should 470 * be cleared when the connection is returned to its pool. Default is false. 471 * 472 * @param clearStatementPoolOnReturn clear or not 473 * @since 2.8.0 474 */ 475 public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) { 476 this.clearStatementPoolOnReturn = clearStatementPoolOnReturn; 477 } 478 479 /** 480 * Sets the SQL statements I use to initialize newly created {@link Connection}s. Using {@code null} turns off 481 * connection initialization. 482 * 483 * @param connectionInitSqls 484 * SQL statement to initialize {@link Connection}s. 485 */ 486 public void setConnectionInitSql(final Collection<String> connectionInitSqls) { 487 this.connectionInitSqls = connectionInitSqls; 488 } 489 /** 490 * Sets the default "auto commit" setting for borrowed {@link Connection}s 491 * 492 * @param defaultAutoCommit 493 * the default "auto commit" setting for borrowed {@link Connection}s 494 */ 495 public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { 496 this.defaultAutoCommit = defaultAutoCommit; 497 } 498 499 /** 500 * Sets the default "catalog" setting for borrowed {@link Connection}s 501 * 502 * @param defaultCatalog 503 * the default "catalog" setting for borrowed {@link Connection}s 504 */ 505 public void setDefaultCatalog(final String defaultCatalog) { 506 this.defaultCatalog = defaultCatalog; 507 } 508 509 public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { 510 this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds; 511 } 512 513 /** 514 * Sets the default "read only" setting for borrowed {@link Connection}s 515 * 516 * @param defaultReadOnly 517 * the default "read only" setting for borrowed {@link Connection}s 518 */ 519 public void setDefaultReadOnly(final Boolean defaultReadOnly) { 520 this.defaultReadOnly = defaultReadOnly; 521 } 522 523 /** 524 * Sets the default "schema" setting for borrowed {@link Connection}s 525 * 526 * @param defaultSchema 527 * the default "schema" setting for borrowed {@link Connection}s 528 * @since 2.5.0 529 */ 530 public void setDefaultSchema(final String defaultSchema) { 531 this.defaultSchema = defaultSchema; 532 } 533 534 /** 535 * Sets the default "Transaction Isolation" setting for borrowed {@link Connection}s 536 * 537 * @param defaultTransactionIsolation 538 * the default "Transaction Isolation" setting for returned {@link Connection}s 539 */ 540 public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { 541 this.defaultTransactionIsolation = defaultTransactionIsolation; 542 } 543 544 /** 545 * @param disconnectionSqlCodes 546 * The disconnection SQL codes. 547 * @see #getDisconnectionSqlCodes() 548 * @since 2.1 549 */ 550 public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) { 551 this.disconnectionSqlCodes = disconnectionSqlCodes; 552 } 553 554 /** 555 * @param autoCommitOnReturn Whether to auto-commit on return. 556 * @deprecated Use {@link #setAutoCommitOnReturn(boolean)}. 557 */ 558 @Deprecated 559 public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) { 560 this.autoCommitOnReturn = autoCommitOnReturn; 561 } 562 563 /** 564 * @see #isFastFailValidation() 565 * @param fastFailValidation 566 * true means connections created by this factory will fast fail validation 567 * @since 2.1 568 */ 569 public void setFastFailValidation(final boolean fastFailValidation) { 570 this.fastFailValidation = fastFailValidation; 571 } 572 573 /** 574 * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation, 575 * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1. 576 * 577 * @param maxConnLifetimeMillis 578 * The maximum lifetime in milliseconds. 579 */ 580 public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { 581 this.maxConnLifetimeMillis = maxConnLifetimeMillis; 582 } 583 584 /** 585 * Sets the maximum number of open prepared statements. 586 * 587 * @param maxOpenPreparedStatements 588 * The maximum number of open prepared statements. 589 */ 590 public void setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) { 591 this.maxOpenPreparedStatements = maxOpenPreparedStatements; 592 } 593 594 /** 595 * Deprecated due to typo in method name. 596 * 597 * @param maxOpenPreparedStatements 598 * The maximum number of open prepared statements. 599 * @deprecated Use {@link #setMaxOpenPreparedStatements(int)}. 600 */ 601 @Deprecated // Due to typo in method name. 602 public void setMaxOpenPrepatedStatements(final int maxOpenPreparedStatements) { 603 setMaxOpenPreparedStatements(maxOpenPreparedStatements); 604 } 605 606 /** 607 * Sets the {@link ObjectPool} in which to pool {@link Connection}s. 608 * 609 * @param pool 610 * the {@link ObjectPool} in which to pool those {@link Connection}s 611 */ 612 public synchronized void setPool(final ObjectPool<PoolableConnection> pool) { 613 if (null != this.pool && pool != this.pool) { 614 Utils.closeQuietly(this.pool); 615 } 616 this.pool = pool; 617 } 618 619 public void setPoolStatements(final boolean poolStatements) { 620 this.poolStatements = poolStatements; 621 } 622 623 public void setRollbackOnReturn(final boolean rollbackOnReturn) { 624 this.rollbackOnReturn = rollbackOnReturn; 625 } 626 627 /** 628 * Sets the query I use to {@link #validateObject validate} {@link Connection}s. Should return at least one row. If 629 * not specified, {@link Connection#isValid(int)} will be used to validate connections. 630 * 631 * @param validationQuery 632 * a query to use to {@link #validateObject validate} {@link Connection}s. 633 */ 634 public void setValidationQuery(final String validationQuery) { 635 this.validationQuery = validationQuery; 636 } 637 638 /** 639 * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a 640 * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. 641 * 642 * @param validationQueryTimeoutSeconds 643 * new validation query timeout value in seconds 644 */ 645 public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { 646 this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds; 647 } 648 649 public void validateConnection(final PoolableConnection conn) throws SQLException { 650 if (conn.isClosed()) { 651 throw new SQLException("validateConnection: connection closed"); 652 } 653 conn.validate(validationQuery, validationQueryTimeoutSeconds); 654 } 655 656 private void validateLifetime(final PooledObject<PoolableConnection> p) throws Exception { 657 if (maxConnLifetimeMillis > 0) { 658 final long lifetimeMillis = System.currentTimeMillis() - p.getCreateTime(); 659 if (lifetimeMillis > maxConnLifetimeMillis) { 660 throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded", 661 lifetimeMillis, maxConnLifetimeMillis)); 662 } 663 } 664 } 665 666 @Override 667 public boolean validateObject(final PooledObject<PoolableConnection> p) { 668 try { 669 validateLifetime(p); 670 671 validateConnection(p.getObject()); 672 return true; 673 } catch (final Exception e) { 674 if (log.isDebugEnabled()) { 675 log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e); 676 } 677 return false; 678 } 679 } 680}