HHH-17003 Race conditions in LazyLoadingConnectionCloseTest and ConnectionsReleaseAutoCommitTest
This commit is contained in:
parent
b561e64fca
commit
d8363d720a
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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?!" );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue