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

org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or .
 */
package org.hibernate.engine.jdbc.connections.internal;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.hibernate.HibernateException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.log.ConnectionPoolingLogger;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.spi.Configurable;
import org.hibernate.service.spi.ServiceException;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.service.spi.Stoppable;

/**
 * A connection provider that uses the {@link java.sql.DriverManager} directly to open connections and provides
 * a very rudimentary connection pool.
 * 

* IMPL NOTE : not intended for production use! *

* Thanks to Oleg Varaksin and his article on object pooling using the {@link java.util.concurrent} package, from * which much of the pooling code here is derived. See http://ovaraksin.blogspot.com/2013/08/simple-and-lightweight-pool.html * * @author Gavin King * @author Steve Ebersole */ public class DriverManagerConnectionProviderImpl implements ConnectionProvider, Configurable, Stoppable, ServiceRegistryAwareService, ConnectionValidator { private static final ConnectionPoolingLogger log = ConnectionPoolingLogger.CONNECTIONS_LOGGER; public static final String MIN_SIZE = "hibernate.connection.min_pool_size"; public static final String INITIAL_SIZE = "hibernate.connection.initial_pool_size"; // in TimeUnit.SECONDS public static final String VALIDATION_INTERVAL = "hibernate.connection.pool_validation_interval"; private volatile PoolState state; // create the pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private volatile ServiceRegistryImplementor serviceRegistry; @Override public void injectServices(ServiceRegistryImplementor serviceRegistry) { this.serviceRegistry = serviceRegistry; } @Override public void configure(Map configurationValues) { log.usingHibernateBuiltInConnectionPool(); PooledConnections pool = buildPool( configurationValues, serviceRegistry ); final long validationInterval = ConfigurationHelper.getLong( VALIDATION_INTERVAL, configurationValues, 30 ); PoolState newstate = new PoolState( pool, validationInterval ); this.state = newstate; } private PooledConnections buildPool(Map configurationValues, ServiceRegistryImplementor serviceRegistry) { final boolean autoCommit = ConfigurationHelper.getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues, false ); final int minSize = ConfigurationHelper.getInt( MIN_SIZE, configurationValues, 1 ); final int maxSize = ConfigurationHelper.getInt( AvailableSettings.POOL_SIZE, configurationValues, 20 ); final int initialSize = ConfigurationHelper.getInt( INITIAL_SIZE, configurationValues, minSize ); ConnectionCreator connectionCreator = buildCreator( configurationValues, serviceRegistry ); PooledConnections.Builder pooledConnectionBuilder = new PooledConnections.Builder( connectionCreator, autoCommit ); pooledConnectionBuilder.initialSize( initialSize ); pooledConnectionBuilder.minSize( minSize ); pooledConnectionBuilder.maxSize( maxSize ); pooledConnectionBuilder.validator( this ); return pooledConnectionBuilder.build(); } private static ConnectionCreator buildCreator(Map configurationValues, ServiceRegistryImplementor serviceRegistry) { final ConnectionCreatorBuilder connectionCreatorBuilder = new ConnectionCreatorBuilder( serviceRegistry ); final String driverClassName = (String) configurationValues.get( AvailableSettings.DRIVER ); connectionCreatorBuilder.setDriver( loadDriverIfPossible( driverClassName, serviceRegistry ) ); final String url = (String) configurationValues.get( AvailableSettings.URL ); if ( url == null ) { final String msg = log.jdbcUrlNotSpecified( AvailableSettings.URL ); log.error( msg ); throw new HibernateException( msg ); } connectionCreatorBuilder.setUrl( url ); log.usingDriver( driverClassName, url ); final Properties connectionProps = ConnectionProviderInitiator.getConnectionProperties( configurationValues ); // if debug level is enabled, then log the password, otherwise mask it if ( log.isDebugEnabled() ) { log.connectionProperties( connectionProps ); } else { log.connectionProperties( ConfigurationHelper.maskOut( connectionProps, "password" ) ); } connectionCreatorBuilder.setConnectionProps( connectionProps ); final boolean autoCommit = ConfigurationHelper.getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues, false ); log.autoCommitMode( autoCommit ); connectionCreatorBuilder.setAutoCommit( autoCommit ); final Integer isolation = ConnectionProviderInitiator.extractIsolation( configurationValues ); if ( isolation != null ) { log.jdbcIsolationLevel( ConnectionProviderInitiator.toIsolationNiceName( isolation ) ); } connectionCreatorBuilder.setIsolation( isolation ); return connectionCreatorBuilder.build(); } private static Driver loadDriverIfPossible(String driverClassName, ServiceRegistryImplementor serviceRegistry) { if ( driverClassName == null ) { log.debug( "No driver class specified" ); return null; } if ( serviceRegistry != null ) { final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); final Class driverClass = classLoaderService.classForName( driverClassName ); try { return driverClass.newInstance(); } catch ( Exception e ) { throw new ServiceException( "Specified JDBC Driver " + driverClassName + " could not be loaded", e ); } } try { return (Driver) Class.forName( driverClassName ).newInstance(); } catch ( Exception e1 ) { throw new ServiceException( "Specified JDBC Driver " + driverClassName + " could not be loaded", e1 ); } } // use the pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public Connection getConnection() throws SQLException { if ( state == null ) { throw new IllegalStateException( "Cannot get a connection as the driver manager is not properly initialized" ); } return state.getConnection(); } @Override public void closeConnection(Connection conn) throws SQLException { if ( state == null ) { throw new IllegalStateException( "Cannot close a connection as the driver manager is not properly initialized" ); } state.closeConnection( conn ); } @Override public boolean supportsAggressiveRelease() { return false; } @Override public boolean isUnwrappableAs(Class unwrapType) { return ConnectionProvider.class.equals( unwrapType ) || DriverManagerConnectionProviderImpl.class.isAssignableFrom( unwrapType ); } @Override @SuppressWarnings( {"unchecked"}) public T unwrap(Class unwrapType) { if ( ConnectionProvider.class.equals( unwrapType ) || DriverManagerConnectionProviderImpl.class.isAssignableFrom( unwrapType ) ) { return (T) this; } else { throw new UnknownUnwrapTypeException( unwrapType ); } } protected void validateConnectionsReturned() { int allocationCount = state.pool.allConnections.size() - state.pool.availableConnections.size(); if ( allocationCount != 0 ) { log.error( "Connection leak detected: there are " + allocationCount + " unclosed connections!"); } } // destroy the pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public void stop() { if ( state != null ) { state.stop(); validateConnectionsReturned(); } } //CHECKSTYLE:START_ALLOW_FINALIZER @Override protected void finalize() throws Throwable { if ( state != null ) { state.stop(); } super.finalize(); } //CHECKSTYLE:END_ALLOW_FINALIZER /** * Exposed to facilitate testing only. * @return */ public Properties getConnectionProperties() { BasicConnectionCreator connectionCreator = (BasicConnectionCreator) this.state.pool.connectionCreator; return connectionCreator.getConnectionProperties(); } @Override public boolean isValid(Connection connection) throws SQLException { return true; } public static class PooledConnections { private final ConcurrentLinkedQueue allConnections = new ConcurrentLinkedQueue(); private final ConcurrentLinkedQueue availableConnections = new ConcurrentLinkedQueue(); private static final CoreMessageLogger log = CoreLogging.messageLogger( DriverManagerConnectionProviderImpl.class ); private final ConnectionCreator connectionCreator; private final ConnectionValidator connectionValidator; private final boolean autoCommit; private final int minSize; private final int maxSize; private volatile boolean primed; private PooledConnections( Builder builder) { log.debugf( "Initializing Connection pool with %s Connections", builder.initialSize ); connectionCreator = builder.connectionCreator; connectionValidator = builder.connectionValidator == null ? ConnectionValidator.ALWAYS_VALID : builder.connectionValidator; autoCommit = builder.autoCommit; maxSize = builder.maxSize; minSize = builder.minSize; log.hibernateConnectionPoolSize( maxSize, minSize ); addConnections( builder.initialSize ); } public void validate() { final int size = size(); if ( !primed && size >= minSize ) { // IMPL NOTE : the purpose of primed is to allow the pool to lazily reach its // defined min-size. log.debug( "Connection pool now considered primed; min-size will be maintained" ); primed = true; } if ( size < minSize && primed ) { int numberToBeAdded = minSize - size; log.debugf( "Adding %s Connections to the pool", numberToBeAdded ); addConnections( numberToBeAdded ); } else if ( size > maxSize ) { int numberToBeRemoved = size - maxSize; log.debugf( "Removing %s Connections from the pool", numberToBeRemoved ); removeConnections( numberToBeRemoved ); } } public void add(Connection conn) throws SQLException { final Connection connection = releaseConnection( conn ); if ( connection != null ) { availableConnections.offer( connection ); } } protected Connection releaseConnection(Connection conn) { Exception t = null; try { conn.setAutoCommit( true ); conn.clearWarnings(); if ( connectionValidator.isValid( conn ) ) { return conn; } } catch (SQLException ex) { t = ex; } closeConnection( conn, t ); log.debug( "Connection release failed. Closing pooled connection", t ); return null; } public Connection poll() throws SQLException { Connection conn; do { conn = availableConnections.poll(); if ( conn == null ) { synchronized (allConnections) { if ( allConnections.size() < maxSize ) { addConnections( 1 ); return poll(); } } throw new HibernateException( "The internal connection pool has reached its maximum size and no connection is currently available!" ); } conn = prepareConnection( conn ); } while ( conn == null ); return conn; } protected Connection prepareConnection(Connection conn) { Exception t = null; try { conn.setAutoCommit( autoCommit ); if ( connectionValidator.isValid( conn ) ) { return conn; } } catch (SQLException ex) { t = ex; } closeConnection( conn, t ); log.debug( "Connection preparation failed. Closing pooled connection", t ); return null; } protected void closeConnection(Connection conn, Throwable t) { try { conn.close(); } catch (SQLException ex) { log.unableToCloseConnection( ex ); if ( t != null ) { t.addSuppressed( ex ); } } finally { allConnections.remove( conn ); } } public void close() throws SQLException { try { int allocationCount = allConnections.size() - availableConnections.size(); if(allocationCount > 0) { log.error( "Connection leak detected: there are " + allocationCount + " unclosed connections upon shutting down pool " + getUrl()); } } finally { for ( Connection connection : allConnections ) { connection.close(); } } } public int size() { return availableConnections.size(); } protected void removeConnections(int numberToBeRemoved) { for ( int i = 0; i < numberToBeRemoved; i++ ) { Connection connection = availableConnections.poll(); try { if ( connection != null ) { connection.close(); } allConnections.remove( connection ); } catch (SQLException e) { log.unableToCloseConnection( e ); } } } protected void addConnections(int numberOfConnections) { for ( int i = 0; i < numberOfConnections; i++ ) { Connection connection = connectionCreator.createConnection(); allConnections.add( connection ); availableConnections.add( connection ); } } public String getUrl() { return connectionCreator.getUrl(); } public static class Builder { private final ConnectionCreator connectionCreator; private ConnectionValidator connectionValidator; private boolean autoCommit; private int initialSize = 1; private int minSize = 1; private int maxSize = 20; public Builder(ConnectionCreator connectionCreator, boolean autoCommit) { this.connectionCreator = connectionCreator; this.autoCommit = autoCommit; } public Builder initialSize(int initialSize) { this.initialSize = initialSize; return this; } public Builder minSize(int minSize) { this.minSize = minSize; return this; } public Builder maxSize(int maxSize) { this.maxSize = maxSize; return this; } public Builder validator(ConnectionValidator connectionValidator) { this.connectionValidator = connectionValidator; return this; } public PooledConnections build() { return new PooledConnections( this ); } } } private static class PoolState { //Protecting any lifecycle state change: private final ReadWriteLock statelock = new ReentrantReadWriteLock(); private volatile boolean active = false; private ScheduledExecutorService executorService; private final PooledConnections pool; private final long validationInterval; public PoolState(PooledConnections pool, long validationInterval) { this.pool = pool; this.validationInterval = validationInterval; } private void startIfNeeded() { if ( active ) { return; } statelock.writeLock().lock(); try { if ( active ) { return; } executorService = Executors.newSingleThreadScheduledExecutor( new ValidationThreadFactory() ); executorService.scheduleWithFixedDelay( pool::validate, validationInterval, validationInterval, TimeUnit.SECONDS ); active = true; } finally { statelock.writeLock().unlock(); } } public void stop() { statelock.writeLock().lock(); try { if ( !active ) { return; } log.cleaningUpConnectionPool( pool.getUrl() ); active = false; if ( executorService != null ) { executorService.shutdown(); } executorService = null; try { pool.close(); } catch (SQLException e) { log.unableToClosePooledConnection( e ); } } finally { statelock.writeLock().unlock(); } } public Connection getConnection() throws SQLException { startIfNeeded(); statelock.readLock().lock(); try { return pool.poll(); } finally { statelock.readLock().unlock(); } } public void closeConnection(Connection conn) throws SQLException { if (conn == null) { return; } startIfNeeded(); statelock.readLock().lock(); try { pool.add( conn ); } finally { statelock.readLock().unlock(); } } } private static class ValidationThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread( runnable ); thread.setDaemon( true ); thread.setName( "Hibernate Connection Pool Validation Thread" ); return thread; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy