HHH-17003 Race conditions in LazyLoadingConnectionCloseTest and ConnectionsReleaseAutoCommitTest

This commit is contained in:
Sanne Grinovero 2023-07-26 16:11:32 +01:00 committed by Sanne Grinovero
parent b561e64fca
commit d8363d720a
7 changed files with 586 additions and 215 deletions

View File

@ -6,22 +6,10 @@
*/
package org.hibernate.orm.test.connections;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Query;
import javax.sql.DataSource;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
@ -30,9 +18,7 @@ import org.hibernate.annotations.LazyCollectionOption;
import org.hibernate.annotations.LazyToOne;
import org.hibernate.annotations.LazyToOneOption;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl;
import org.hibernate.orm.test.jpa.connection.BaseDataSource;
import org.hibernate.orm.test.util.connections.ConnectionCheckingConnectionProvider;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.testing.TestForIssue;
@ -41,9 +27,16 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.spy;
/**
* @author Selaron
@ -51,7 +44,7 @@ import static org.mockito.Mockito.spy;
@TestForIssue(jiraKey = "HHH-4808")
public class LazyLoadingConnectionCloseTest extends EntityManagerFactoryBasedFunctionalTest {
private ConnectionProviderDecorator connectionProvider;
private ConnectionCheckingConnectionProvider connectionProvider;
@Override
protected Class<?>[] getAnnotatedClasses() {
@ -69,7 +62,7 @@ public class LazyLoadingConnectionCloseTest extends EntityManagerFactoryBasedFun
options.put( AvailableSettings.AUTOCOMMIT, "false" );
connectionProvider = new ConnectionProviderDecorator( getDataSource() );
connectionProvider = new ConnectionCheckingConnectionProvider();
options.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider );
}
@ -295,68 +288,4 @@ public class LazyLoadingConnectionCloseTest extends EntityManagerFactoryBasedFun
}
}
private BaseDataSource getDataSource() {
final Properties connectionProps = new Properties();
connectionProps.put( "user", Environment.getProperties().getProperty( Environment.USER ) );
connectionProps.put( "password", Environment.getProperties().getProperty( Environment.PASS ) );
final String url = Environment.getProperties().getProperty( Environment.URL );
return new BaseDataSource() {
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection( url, connectionProps );
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return DriverManager.getConnection( url, connectionProps );
}
};
}
public static class ConnectionProviderDecorator extends UserSuppliedConnectionProviderImpl {
private final DataSource dataSource;
private int connectionCount;
private int openConnections;
private Connection connection;
public ConnectionProviderDecorator(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Connection getConnection() throws SQLException {
connectionCount++;
openConnections++;
connection = spy( dataSource.getConnection() );
return connection;
}
@Override
public void closeConnection(Connection connection) throws SQLException {
connection.close();
openConnections--;
}
public int getTotalOpenedConnectionCount() {
return this.connectionCount;
}
public int getCurrentOpenConnections() {
return openConnections;
}
public boolean areAllConnectionClosed() {
return openConnections == 0;
}
public void clear() {
connectionCount = 0;
openConnections = 0;
}
}
}

View File

@ -1,47 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
//$Id$
package org.hibernate.orm.test.jpa.connection;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource;
/**
* @author Vlad Mihalcea
*/
public abstract class BaseDataSource implements DataSource {
public PrintWriter getLogWriter() throws SQLException {
return new PrintWriter( System.out );
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public <T> T unwrap(Class<T> tClass) throws SQLException {
return (T) this;
}
public boolean isWrapperFor(Class<?> aClass) throws SQLException {
return false;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}

View File

@ -1,68 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.jpa.connection;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl;
import static org.mockito.Mockito.spy;
public class ConnectionProviderDecorator extends UserSuppliedConnectionProviderImpl {
private final DataSource dataSource;
private int connectionCount;
public Connection connection;
public ConnectionProviderDecorator(){
String url = Environment.getProperties().getProperty( Environment.URL );
Properties connectionProps = new Properties();
connectionProps.put( "user", Environment.getProperties().getProperty( Environment.USER ) );
connectionProps.put( "password", Environment.getProperties().getProperty( Environment.PASS ) );
dataSource = new BaseDataSource() {
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection( url, connectionProps );
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return DriverManager.getConnection( url, connectionProps );
}
};
}
@Override
public Connection getConnection() throws SQLException {
connectionCount++;
connection = spy( dataSource.getConnection() );
return connection;
}
@Override
public void closeConnection(Connection connection) throws SQLException {
connection.close();
}
public int getConnectionCount() {
return this.connectionCount;
}
public void clear() {
connectionCount = 0;
}
}

View File

@ -6,15 +6,11 @@
*/
package org.hibernate.orm.test.jpa.connection;
import java.sql.SQLException;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.orm.test.util.connections.ConnectionCheckingConnectionProvider;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
@ -23,11 +19,12 @@ import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Vlad Mihalcea
@ -36,30 +33,30 @@ import static org.mockito.Mockito.verify;
@RequiresDialect(H2Dialect.class)
@Jpa(
annotatedClasses = { ConnectionsReleaseAutoCommitTest.Thing.class },
integrationSettings = @Setting(name = AvailableSettings.CONNECTION_PROVIDER, value = "org.hibernate.orm.test.jpa.connection.ConnectionProviderDecorator")
integrationSettings = @Setting(name = AvailableSettings.CONNECTION_PROVIDER, value = "org.hibernate.orm.test.util.connections.ConnectionCheckingConnectionProvider")
)
public class ConnectionsReleaseAutoCommitTest {
@Test
public void testConnectionAcquisitionCount(EntityManagerFactoryScope scope) throws SQLException {
ConnectionProviderDecorator connectionProvider = getConnectionProvider( scope );
public void testConnectionAcquisitionCount(EntityManagerFactoryScope scope) {
ConnectionCheckingConnectionProvider connectionProvider = getConnectionProvider( scope );
assertTrue( connectionProvider.areAllConnectionClosed() );
connectionProvider.clear();
scope.inTransaction( entityManager -> {
assertEquals( 1, connectionProvider.getConnectionCount() );
assertEquals( 1, connectionProvider.getTotalOpenedConnectionCount() );
Thing thing = new Thing();
thing.setId( 1 );
entityManager.persist( thing );
assertEquals( 1, connectionProvider.getConnectionCount() );
assertEquals( 1, connectionProvider.getTotalOpenedConnectionCount() );
} );
assertEquals( 1, connectionProvider.getConnectionCount() );
verify( connectionProvider.connection, times( 1 ) ).close();
Mockito.reset( connectionProvider.connection );
assertEquals( 1, connectionProvider.getTotalOpenedConnectionCount() );
assertTrue( connectionProvider.areAllConnectionClosed() );
}
private ConnectionProviderDecorator getConnectionProvider(EntityManagerFactoryScope scope) {
return (ConnectionProviderDecorator) ( (SessionFactoryImplementor) ( scope
private ConnectionCheckingConnectionProvider getConnectionProvider(EntityManagerFactoryScope scope) {
return (ConnectionCheckingConnectionProvider) ( (SessionFactoryImplementor) ( scope
.getEntityManagerFactory() ) ).getServiceRegistry().getService( ConnectionProvider.class );
}

View File

@ -0,0 +1,80 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.util.connections;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.hibernate.cfg.Environment;
/**
* Simple {@link DataSource} implementation useful in various integration tests,
* or possibly to be used as base class to extend.
*/
public class BaseDataSource implements DataSource {
private final Properties connectionProperties;
private final String url;
public BaseDataSource(Properties configuration) {
url = configuration.getProperty( Environment.URL );
connectionProperties = new Properties();
connectionProperties.put( "user", configuration.getProperty( Environment.USER ) );
connectionProperties.put( "password", configuration.getProperty( Environment.PASS ) );
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection( url, connectionProperties );
}
@Override
public Connection getConnection(String username, String password) {
throw new UnsupportedOperationException( "method not supported" );
}
@Override
public PrintWriter getLogWriter() {
return new PrintWriter( System.out );
}
@Override
public void setLogWriter(PrintWriter out) {
}
@Override
public void setLoginTimeout(int seconds) {
throw new UnsupportedOperationException( "method not supported" );
}
@Override
public int getLoginTimeout() {
throw new UnsupportedOperationException("method not supported");
}
@Override
public <T> T unwrap(Class<T> tClass) {
return (T) this;
}
@Override
public boolean isWrapperFor(Class<?> aClass) {
return false;
}
@Override
public Logger getParentLogger() {
return null;
}
}

View File

@ -0,0 +1,358 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.util.connections;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.ShardingKey;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* Base delegate implementation of a {@link java.sql.Connection};
* meant to be extended so to override select methods.
*/
public class ConnectionBaseDelegate implements Connection {
private final Connection delegate;
ConnectionBaseDelegate(Connection delegate) {
Objects.requireNonNull( delegate );
this.delegate = delegate;
}
// All methods below are generated by the IDE as standard delegates:
// for maintenance reasons, if you need customizations prefer extending
// this class over modifying it.
@Override
public Statement createStatement() throws SQLException {
return delegate.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return delegate.prepareStatement( sql );
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
return delegate.prepareCall( sql );
}
@Override
public String nativeSQL(String sql) throws SQLException {
return delegate.nativeSQL( sql );
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
delegate.setAutoCommit( autoCommit );
}
@Override
public boolean getAutoCommit() throws SQLException {
return delegate.getAutoCommit();
}
@Override
public void commit() throws SQLException {
delegate.commit();
}
@Override
public void rollback() throws SQLException {
delegate.rollback();
}
@Override
public void close() throws SQLException {
delegate.close();
}
@Override
public boolean isClosed() throws SQLException {
return delegate.isClosed();
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
return delegate.getMetaData();
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
delegate.setReadOnly( readOnly );
}
@Override
public boolean isReadOnly() throws SQLException {
return delegate.isReadOnly();
}
@Override
public void setCatalog(String catalog) throws SQLException {
delegate.setCatalog( catalog );
}
@Override
public String getCatalog() throws SQLException {
return delegate.getCatalog();
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
delegate.setTransactionIsolation( level );
}
@Override
public int getTransactionIsolation() throws SQLException {
return delegate.getTransactionIsolation();
}
@Override
public SQLWarning getWarnings() throws SQLException {
return delegate.getWarnings();
}
@Override
public void clearWarnings() throws SQLException {
delegate.clearWarnings();
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return delegate.createStatement( resultSetType, resultSetConcurrency );
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return delegate.prepareStatement( sql, resultSetType, resultSetConcurrency );
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return delegate.prepareCall( sql, resultSetType, resultSetConcurrency );
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
return delegate.getTypeMap();
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
delegate.setTypeMap( map );
}
@Override
public void setHoldability(int holdability) throws SQLException {
delegate.setHoldability( holdability );
}
@Override
public int getHoldability() throws SQLException {
return delegate.getHoldability();
}
@Override
public Savepoint setSavepoint() throws SQLException {
return delegate.setSavepoint();
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
return delegate.setSavepoint( name );
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
delegate.rollback( savepoint );
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
delegate.releaseSavepoint( savepoint );
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
return delegate.createStatement( resultSetType, resultSetConcurrency, resultSetHoldability );
}
@Override
public PreparedStatement prepareStatement(
String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
return delegate.prepareStatement( sql, resultSetType, resultSetConcurrency, resultSetHoldability );
}
@Override
public CallableStatement prepareCall(
String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
return delegate.prepareCall( sql, resultSetType, resultSetConcurrency, resultSetHoldability );
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
return delegate.prepareStatement( sql, autoGeneratedKeys );
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
return delegate.prepareStatement( sql, columnIndexes );
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
return delegate.prepareStatement( sql, columnNames );
}
@Override
public Clob createClob() throws SQLException {
return delegate.createClob();
}
@Override
public Blob createBlob() throws SQLException {
return delegate.createBlob();
}
@Override
public NClob createNClob() throws SQLException {
return delegate.createNClob();
}
@Override
public SQLXML createSQLXML() throws SQLException {
return delegate.createSQLXML();
}
@Override
public boolean isValid(int timeout) throws SQLException {
return delegate.isValid( timeout );
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
delegate.setClientInfo( name, value );
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
delegate.setClientInfo( properties );
}
@Override
public String getClientInfo(String name) throws SQLException {
return delegate.getClientInfo( name );
}
@Override
public Properties getClientInfo() throws SQLException {
return delegate.getClientInfo();
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
return delegate.createArrayOf( typeName, elements );
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
return delegate.createStruct( typeName, attributes );
}
@Override
public void setSchema(String schema) throws SQLException {
delegate.setSchema( schema );
}
@Override
public String getSchema() throws SQLException {
return delegate.getSchema();
}
@Override
public void abort(Executor executor) throws SQLException {
delegate.abort( executor );
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
delegate.setNetworkTimeout( executor, milliseconds );
}
@Override
public int getNetworkTimeout() throws SQLException {
return delegate.getNetworkTimeout();
}
@Override
public void beginRequest() throws SQLException {
delegate.beginRequest();
}
@Override
public void endRequest() throws SQLException {
delegate.endRequest();
}
@Override
public boolean setShardingKeyIfValid(ShardingKey shardingKey, ShardingKey superShardingKey, int timeout)
throws SQLException {
return delegate.setShardingKeyIfValid( shardingKey, superShardingKey, timeout );
}
@Override
public boolean setShardingKeyIfValid(ShardingKey shardingKey, int timeout) throws SQLException {
return delegate.setShardingKeyIfValid( shardingKey, timeout );
}
@Override
public void setShardingKey(ShardingKey shardingKey, ShardingKey superShardingKey) throws SQLException {
delegate.setShardingKey( shardingKey, superShardingKey );
}
@Override
public void setShardingKey(ShardingKey shardingKey) throws SQLException {
delegate.setShardingKey( shardingKey );
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return delegate.unwrap( iface );
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return delegate.isWrapperFor( iface );
}
}

View File

@ -0,0 +1,122 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.util.connections;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl;
/**
* A {@link org.hibernate.engine.jdbc.connections.spi.ConnectionProvider} implementation
* meant to be configured via {@code AvailableSettings.CONNECTION_PROVIDER} to facilitate
* testing of how many connections are being opened and closed.
* This implementation is thread-safe, however state could be racy: make sure the scenarios
* being tested have completed before assertions are verified.
*/
public final class ConnectionCheckingConnectionProvider extends UserSuppliedConnectionProviderImpl {
private final DataSource dataSource = new BaseDataSource( Environment.getProperties() );
/**
* Counts the "open" events. Does NOT hold the total number of open connections
* existing at a given time, just the amount of times a connection was opened.
*/
private final AtomicInteger connectionOpenEventCount = new AtomicInteger();
//Using a Vector just to avoid synchronizing on a bag
private final Vector<CheckedConnection> openedConnections = new Vector<>();
@Override
public Connection getConnection() throws SQLException {
this.connectionOpenEventCount.incrementAndGet();
final CheckedConnection opened = new CheckedConnection( dataSource.getConnection() );
this.openedConnections.add( opened );
return opened;
}
@Override
public void closeConnection(Connection connection) throws SQLException {
connection.close();
//cast is also meant to verify we're not being returned a different implementation
CheckedConnection wrapper = (CheckedConnection) connection;
boolean removed = this.openedConnections.remove( wrapper );
if ( !removed ) {
throw new IllegalStateException(
"Closing a connection which wasn't registered in this ConnectionProviderDecorator" );
}
}
/**
* Resets the counters to zero; it's useful to invoke this after Hibernate
* has booted to exclude connections being used during initialization.
* @throws IllegalStateException if any unclosed connection are being detected.
*/
public void clear() {
this.connectionOpenEventCount.set( 0 );
if ( !areAllConnectionClosed() ) {
throw new IllegalStateException( "Resetting test helper while not all connections have been closed yet" );
}
}
/**
* @return the count of connections which are currently open.
*/
public int getCurrentOpenConnections() {
return this.openedConnections.size();
}
/**
* @return {@code true} iff all known connections that have been opened are now closed.
*/
public boolean areAllConnectionClosed() {
return this.openedConnections.isEmpty();
}
/**
* @return This returns the count of connections that have been opened since
* construction, or since the last time method {@link #clear()} has
* been invoked. N.B. this count includes connections that have since been closed.
*/
public int getTotalOpenedConnectionCount() {
return this.connectionOpenEventCount.get();
}
private static final class CheckedConnection extends ConnectionBaseDelegate {
private final AtomicBoolean delegateWasClosed = new AtomicBoolean( false );
private CheckedConnection(Connection delegate) {
super( delegate );
}
/**
* Implementation Note: closing the connection by invoking this method directly will not
* unregister it from the #openedConnections vector above:
* this implies that there could be a mismatch, and we leverage this to spot connections
* that have been closed but not using the appropriate method:
* {@link org.hibernate.engine.jdbc.connections.spi.ConnectionProvider#closeConnection(Connection)}
* @throws SQLException on any exception during the close operation
*/
@Override
public void close() throws SQLException {
super.close();
//Safeguard against closing multiple times:
if ( !this.delegateWasClosed.compareAndSet( false, true ) ) {
throw new IllegalStateException( "Was already closed?!" );
}
}
}
}