All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.dbcp2.PoolableConnectionFactory Maven / Gradle / Ivy

There is a newer version: 2.12.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.dbcp2;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;

import javax.management.ObjectName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool2.KeyedObjectPool;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;

/**
 * A {@link PooledObjectFactory} that creates {@link PoolableConnection}s.
 *
 * @since 2.0
 */
public class PoolableConnectionFactory implements PooledObjectFactory {

    private static final Log log = LogFactory.getLog(PoolableConnectionFactory.class);

    /**
     * Internal constant to indicate the level is not set.
     */
    static final int UNKNOWN_TRANSACTION_ISOLATION = -1;

    private final ConnectionFactory connectionFactory;

    private final ObjectName dataSourceJmxObjectName;

    private volatile String validationQuery;

    private volatile int validationQueryTimeoutSeconds = -1;

    private Collection connectionInitSqls;

    private Collection disconnectionSqlCodes;

    private boolean fastFailValidation = true;

    private volatile ObjectPool pool;

    private Boolean defaultReadOnly;

    private Boolean defaultAutoCommit;

    private boolean autoCommitOnReturn = true;

    private boolean rollbackOnReturn = true;

    private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION;

    private String defaultCatalog;

    private String defaultSchema;

    private boolean cacheState;

    private boolean poolStatements;

    private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;

    private long maxConnLifetimeMillis = -1;

    private final AtomicLong connectionIndex = new AtomicLong(0);

    private Integer defaultQueryTimeoutSeconds;

    /**
     * Creates a new {@code PoolableConnectionFactory}.
     *
     * @param connFactory
     *            the {@link ConnectionFactory} from which to obtain base {@link Connection}s
     * @param dataSourceJmxObjectName
     *            The JMX object name, may be null.
     */
    public PoolableConnectionFactory(final ConnectionFactory connFactory, final ObjectName dataSourceJmxObjectName) {
        this.connectionFactory = connFactory;
        this.dataSourceJmxObjectName = dataSourceJmxObjectName;
    }

    @Override
    public void activateObject(final PooledObject p) throws Exception {

        validateLifetime(p);

        final PoolableConnection conn = p.getObject();
        conn.activate();

        if (defaultAutoCommit != null && conn.getAutoCommit() != defaultAutoCommit.booleanValue()) {
            conn.setAutoCommit(defaultAutoCommit.booleanValue());
        }
        if (defaultTransactionIsolation != UNKNOWN_TRANSACTION_ISOLATION
                && conn.getTransactionIsolation() != defaultTransactionIsolation) {
            conn.setTransactionIsolation(defaultTransactionIsolation);
        }
        if (defaultReadOnly != null && conn.isReadOnly() != defaultReadOnly.booleanValue()) {
            conn.setReadOnly(defaultReadOnly.booleanValue());
        }
        if (defaultCatalog != null && !defaultCatalog.equals(conn.getCatalog())) {
            conn.setCatalog(defaultCatalog);
        }
        if (defaultSchema != null && !defaultSchema.equals(Jdbc41Bridge.getSchema(conn))) {
            Jdbc41Bridge.setSchema(conn, defaultSchema);
        }
        conn.setDefaultQueryTimeout(defaultQueryTimeoutSeconds);
    }

    @Override
    public void destroyObject(final PooledObject p) throws Exception {
        p.getObject().reallyClose();
    }

    /**
     * @return The cache state.
     * @since Made public in 2.6.0.
     */
    public boolean getCacheState() {
        return cacheState;
    }

    /**
     * @return The connection factory.
     * @since Made public in 2.6.0.
     */
    public ConnectionFactory getConnectionFactory() {
        return connectionFactory;
    }

    protected AtomicLong getConnectionIndex() {
        return connectionIndex;
    }

    /**
     * @return The collection of initialization SQL statements.
     * @since 2.6.0
     */
    public Collection getConnectionInitSqls() {
        return connectionInitSqls;
    }

    /**
     * @return The data source JMX ObjectName
     * @since Made public in 2.6.0.
     */
    public ObjectName getDataSourceJmxName() {
        return dataSourceJmxObjectName;
    }

    /**
     * @return The data source JMS ObjectName.
     * @since 2.6.0
     */
    public ObjectName getDataSourceJmxObjectName() {
        return dataSourceJmxObjectName;
    }

    /**
     * @return Default auto-commit value.
     * @since 2.6.0
     */
    public Boolean getDefaultAutoCommit() {
        return defaultAutoCommit;
    }

    /**
     * @return Default catalog.
     * @since 2.6.0
     */
    public String getDefaultCatalog() {
        return defaultCatalog;
    }

    /**
     * @return Default query timeout in seconds.
     */
    public Integer getDefaultQueryTimeout() {
        return defaultQueryTimeoutSeconds;
    }

    /**
     * @return Default query timeout in seconds.
     * @since 2.6.0
     */
    public Integer getDefaultQueryTimeoutSeconds() {
        return defaultQueryTimeoutSeconds;
    }

    /**
     * @return Default read-only-value.
     * @since 2.6.0
     */
    public Boolean getDefaultReadOnly() {
        return defaultReadOnly;
    }

    /**
     * @return Default schema.
     * @since 2.6.0
     */
    public String getDefaultSchema() {
        return defaultSchema;
    }

    /**
     * @return Default transaction isolation.
     * @since 2.6.0
     */
    public int getDefaultTransactionIsolation() {
        return defaultTransactionIsolation;
    }

    /**
     * SQL_STATE codes considered to signal fatal conditions.
     * 

* Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is * {@code true}, whenever connections created by this factory generate exceptions with SQL_STATE codes in this list, * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or * validation query). *

*

* If {@link #isFastFailValidation()} is {@code false} setting this property has no effect. *

* * @return SQL_STATE codes overriding defaults * @since 2.1 */ public Collection getDisconnectionSqlCodes() { return disconnectionSqlCodes; } /** * @return Maximum connection lifetime in milliseconds. * @since 2.6.0 */ public long getMaxConnLifetimeMillis() { return maxConnLifetimeMillis; } protected int getMaxOpenPreparedStatements() { return maxOpenPreparedStatements; } /** * Returns the {@link ObjectPool} in which {@link Connection}s are pooled. * * @return the connection pool */ public synchronized ObjectPool getPool() { return pool; } /** * @return Whether to pool statements. * @since Made public in 2.6.0. */ public boolean getPoolStatements() { return poolStatements; } /** * @return Validation query. * @since 2.6.0 */ public String getValidationQuery() { return validationQuery; } /** * @return Validation query timeout in seconds. * @since 2.6.0 */ public int getValidationQueryTimeoutSeconds() { return validationQueryTimeoutSeconds; } protected void initializeConnection(final Connection conn) throws SQLException { final Collection sqls = connectionInitSqls; if (conn.isClosed()) { throw new SQLException("initializeConnection: connection closed"); } if (null != sqls) { try (Statement stmt = conn.createStatement()) { for (final String sql : sqls) { Objects.requireNonNull(sql, "null connectionInitSqls element"); stmt.execute(sql); } } } } /** * @return Whether to auto-commit on return. * @since 2.6.0 */ public boolean isAutoCommitOnReturn() { return autoCommitOnReturn; } /** * @return Whether to auto-commit on return. * @deprecated Use {@link #isAutoCommitOnReturn()}. */ @Deprecated public boolean isEnableAutoCommitOnReturn() { return autoCommitOnReturn; } /** * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with * SQL_STATE indicating fatal disconnection errors. * * @return true if connections created by this factory will fast fail validation. * @see #setDisconnectionSqlCodes(Collection) * @since 2.1 * @since 2.5.0 Defaults to true, previous versions defaulted to false. */ public boolean isFastFailValidation() { return fastFailValidation; } /** * @return Whether to rollback on return. */ public boolean isRollbackOnReturn() { return rollbackOnReturn; } @Override public PooledObject makeObject() throws Exception { Connection conn = connectionFactory.createConnection(); if (conn == null) { throw new IllegalStateException("Connection factory returned null from createConnection"); } try { initializeConnection(conn); } catch (final SQLException sqle) { // Make sure the connection is closed try { conn.close(); } catch (final SQLException ignore) { // ignore } // Rethrow original exception so it is visible to caller throw sqle; } final long connIndex = connectionIndex.getAndIncrement(); if (poolStatements) { conn = new PoolingConnection(conn); final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig<>(); config.setMaxTotalPerKey(-1); config.setBlockWhenExhausted(false); config.setMaxWaitMillis(0); config.setMaxIdlePerKey(1); config.setMaxTotal(maxOpenPreparedStatements); if (dataSourceJmxObjectName != null) { final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString()); base.append(Constants.JMX_CONNECTION_BASE_EXT); base.append(Long.toString(connIndex)); config.setJmxNameBase(base.toString()); config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); } else { config.setJmxEnabled(false); } final PoolingConnection poolingConn = (PoolingConnection) conn; final KeyedObjectPool stmtPool = new GenericKeyedObjectPool<>( poolingConn, config); poolingConn.setStatementPool(stmtPool); poolingConn.setCacheState(cacheState); } // Register this connection with JMX ObjectName connJmxName; if (dataSourceJmxObjectName == null) { connJmxName = null; } else { connJmxName = new ObjectName( dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex); } final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes, fastFailValidation); pc.setCacheState(cacheState); return new DefaultPooledObject<>(pc); } @Override public void passivateObject(final PooledObject p) throws Exception { validateLifetime(p); final PoolableConnection conn = p.getObject(); Boolean connAutoCommit = null; if (rollbackOnReturn) { connAutoCommit = Boolean.valueOf(conn.getAutoCommit()); if (!connAutoCommit.booleanValue() && !conn.isReadOnly()) { conn.rollback(); } } conn.clearWarnings(); // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should // have autoCommit enabled if (autoCommitOnReturn) { if (connAutoCommit == null) { connAutoCommit = Boolean.valueOf(conn.getAutoCommit()); } if (!connAutoCommit.booleanValue()) { conn.setAutoCommit(true); } } conn.passivate(); } public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) { this.autoCommitOnReturn = autoCommitOnReturn; } public void setCacheState(final boolean cacheState) { this.cacheState = cacheState; } /** * Sets the SQL statements I use to initialize newly created {@link Connection}s. Using {@code null} turns off * connection initialization. * * @param connectionInitSqls * SQL statement to initialize {@link Connection}s. */ public void setConnectionInitSql(final Collection connectionInitSqls) { this.connectionInitSqls = connectionInitSqls; } /** * Sets the default "auto commit" setting for borrowed {@link Connection}s * * @param defaultAutoCommit * the default "auto commit" setting for borrowed {@link Connection}s */ public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { this.defaultAutoCommit = defaultAutoCommit; } /** * Sets the default "catalog" setting for borrowed {@link Connection}s * * @param defaultCatalog * the default "catalog" setting for borrowed {@link Connection}s */ public void setDefaultCatalog(final String defaultCatalog) { this.defaultCatalog = defaultCatalog; } public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds; } /** * Sets the default "read only" setting for borrowed {@link Connection}s * * @param defaultReadOnly * the default "read only" setting for borrowed {@link Connection}s */ public void setDefaultReadOnly(final Boolean defaultReadOnly) { this.defaultReadOnly = defaultReadOnly; } /** * Sets the default "schema" setting for borrowed {@link Connection}s * * @param defaultSchema * the default "schema" setting for borrowed {@link Connection}s * @since 2.5.0 */ public void setDefaultSchema(final String defaultSchema) { this.defaultSchema = defaultSchema; } /** * Sets the default "Transaction Isolation" setting for borrowed {@link Connection}s * * @param defaultTransactionIsolation * the default "Transaction Isolation" setting for returned {@link Connection}s */ public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { this.defaultTransactionIsolation = defaultTransactionIsolation; } /** * @param disconnectionSqlCodes * The disconnection SQL codes. * @see #getDisconnectionSqlCodes() * @since 2.1 */ public void setDisconnectionSqlCodes(final Collection disconnectionSqlCodes) { this.disconnectionSqlCodes = disconnectionSqlCodes; } /** * @param autoCommitOnReturn Whether to auto-commit on return. * @deprecated Use {@link #setAutoCommitOnReturn(boolean)}. */ @Deprecated public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) { this.autoCommitOnReturn = autoCommitOnReturn; } /** * @see #isFastFailValidation() * @param fastFailValidation * true means connections created by this factory will fast fail validation * @since 2.1 */ public void setFastFailValidation(final boolean fastFailValidation) { this.fastFailValidation = fastFailValidation; } /** * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation, * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1. * * @param maxConnLifetimeMillis * The maximum lifetime in milliseconds. */ public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { this.maxConnLifetimeMillis = maxConnLifetimeMillis; } /** * Sets the maximum number of open prepared statements. * * @param maxOpenPreparedStatements * The maximum number of open prepared statements. */ public void setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) { this.maxOpenPreparedStatements = maxOpenPreparedStatements; } /** * Deprecated due to typo in method name. * * @param maxOpenPreparedStatements * The maximum number of open prepared statements. * @deprecated Use {@link #setMaxOpenPreparedStatements(int)}. */ @Deprecated // Due to typo in method name. public void setMaxOpenPrepatedStatements(final int maxOpenPreparedStatements) { setMaxOpenPreparedStatements(maxOpenPreparedStatements); } /** * Sets the {@link ObjectPool} in which to pool {@link Connection}s. * * @param pool * the {@link ObjectPool} in which to pool those {@link Connection}s */ public synchronized void setPool(final ObjectPool pool) { if (null != this.pool && pool != this.pool) { try { this.pool.close(); } catch (final Exception e) { // ignored !?! } } this.pool = pool; } public void setPoolStatements(final boolean poolStatements) { this.poolStatements = poolStatements; } public void setRollbackOnReturn(final boolean rollbackOnReturn) { this.rollbackOnReturn = rollbackOnReturn; } /** * Sets the query I use to {@link #validateObject validate} {@link Connection}s. Should return at least one row. If * not specified, {@link Connection#isValid(int)} will be used to validate connections. * * @param validationQuery * a query to use to {@link #validateObject validate} {@link Connection}s. */ public void setValidationQuery(final String validationQuery) { this.validationQuery = validationQuery; } /** * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout. * * @param validationQueryTimeoutSeconds * new validation query timeout value in seconds */ public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) { this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds; } public void validateConnection(final PoolableConnection conn) throws SQLException { if (conn.isClosed()) { throw new SQLException("validateConnection: connection closed"); } conn.validate(validationQuery, validationQueryTimeoutSeconds); } private void validateLifetime(final PooledObject p) throws Exception { if (maxConnLifetimeMillis > 0) { final long lifetime = System.currentTimeMillis() - p.getCreateTime(); if (lifetime > maxConnLifetimeMillis) { throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded", Long.valueOf(lifetime), Long.valueOf(maxConnLifetimeMillis))); } } } @Override public boolean validateObject(final PooledObject p) { try { validateLifetime(p); validateConnection(p.getObject()); return true; } catch (final Exception e) { if (log.isDebugEnabled()) { log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e); } return false; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy