From d8363d720a1b8fbd97cc45d356176314cb2b6c1d Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 26 Jul 2023 16:11:32 +0100 Subject: [PATCH] HHH-17003 Race conditions in LazyLoadingConnectionCloseTest and ConnectionsReleaseAutoCommitTest --- .../LazyLoadingConnectionCloseTest.java | 93 +---- .../test/jpa/connection/BaseDataSource.java | 47 --- .../ConnectionProviderDecorator.java | 68 ---- .../ConnectionsReleaseAutoCommitTest.java | 33 +- .../test/util/connections/BaseDataSource.java | 80 ++++ .../connections/ConnectionBaseDelegate.java | 358 ++++++++++++++++++ .../ConnectionCheckingConnectionProvider.java | 122 ++++++ 7 files changed, 586 insertions(+), 215 deletions(-) delete mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/BaseDataSource.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/ConnectionProviderDecorator.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/BaseDataSource.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/ConnectionBaseDelegate.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/ConnectionCheckingConnectionProvider.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/connections/LazyLoadingConnectionCloseTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/connections/LazyLoadingConnectionCloseTest.java index 6f1616c4ba..0afb2a0e3d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/connections/LazyLoadingConnectionCloseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/connections/LazyLoadingConnectionCloseTest.java @@ -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; - } - } - } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/BaseDataSource.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/BaseDataSource.java deleted file mode 100644 index 9771e8b9e3..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/BaseDataSource.java +++ /dev/null @@ -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 . - */ - -//$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 unwrap(Class tClass) throws SQLException { - return (T) this; - } - - public boolean isWrapperFor(Class aClass) throws SQLException { - return false; - } - - public Logger getParentLogger() throws SQLFeatureNotSupportedException { - return null; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/ConnectionProviderDecorator.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/ConnectionProviderDecorator.java deleted file mode 100644 index 97a1a803c2..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/ConnectionProviderDecorator.java +++ /dev/null @@ -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 . - */ -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; - } -} - diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/ConnectionsReleaseAutoCommitTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/ConnectionsReleaseAutoCommitTest.java index 3276cc543e..d25181f096 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/ConnectionsReleaseAutoCommitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/connection/ConnectionsReleaseAutoCommitTest.java @@ -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 ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/BaseDataSource.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/BaseDataSource.java new file mode 100644 index 0000000000..7b35e4cacd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/BaseDataSource.java @@ -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 . + */ + +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 unwrap(Class tClass) { + return (T) this; + } + + @Override + public boolean isWrapperFor(Class aClass) { + return false; + } + + @Override + public Logger getParentLogger() { + return null; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/ConnectionBaseDelegate.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/ConnectionBaseDelegate.java new file mode 100644 index 0000000000..ff153684f0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/ConnectionBaseDelegate.java @@ -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 . + */ +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> getTypeMap() throws SQLException { + return delegate.getTypeMap(); + } + + @Override + public void setTypeMap(Map> 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 unwrap(Class iface) throws SQLException { + return delegate.unwrap( iface ); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return delegate.isWrapperFor( iface ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/ConnectionCheckingConnectionProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/ConnectionCheckingConnectionProvider.java new file mode 100644 index 0000000000..20d65d03d5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/connections/ConnectionCheckingConnectionProvider.java @@ -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 . + */ +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 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?!" ); + } + } + + } + +} +