HHH-11542 - Allow the auto-commit resolution to be configurable for RESOURCE_LOCAL transactions

This commit is contained in:
Vlad Mihalcea 2017-04-12 16:31:27 +03:00 committed by Andrea Boriero
parent 23aad981a8
commit ca103dcb28
17 changed files with 667 additions and 25 deletions

View File

@ -54,6 +54,14 @@ Note that for backwards compatibility, if a https://docs.jboss.org/hibernate/orm
|`hibernate.connection.isolation` | `REPEATABLE_READ` or |`hibernate.connection.isolation` | `REPEATABLE_READ` or
`Connection.TRANSACTION_REPEATABLE_READ` | Names the JDBC connection transaction isolation level. `Connection.TRANSACTION_REPEATABLE_READ` | Names the JDBC connection transaction isolation level.
|`hibernate.connection.autocommit` | `true` or `false` (default value) | Names the JDBC connection autocommit mode. |`hibernate.connection.autocommit` | `true` or `false` (default value) | Names the JDBC connection autocommit mode.
|`hibernate.connection.skip_autocommit_check` | `true` or `false` (default value) |
When using an external non-JTA `DataSource`, it might be that the underlying `DataSource` already disables the autocommit mode,
so there is no need to check this `Connection` attribute upon starting a RESOURCE_LOCAL transaction.
By setting it to `true`, the `Connection` acquisition can be delayed until the first SQL statement is needed to be executed.
The connection acquisition delay allows you to reduce the database connection lease time, therefore allowing you to increase the transaction throughput.
|`hibernate.connection.datasource` | | |`hibernate.connection.datasource` | |
Either a `javax.sql.DataSource` instance or a JNDI name under which to locate the `DataSource`. Either a `javax.sql.DataSource` instance or a JNDI name under which to locate the `DataSource`.

View File

@ -342,6 +342,14 @@ public interface AvailableSettings {
*/ */
String DATASOURCE ="hibernate.connection.datasource"; String DATASOURCE ="hibernate.connection.datasource";
/**
* Instructs Hibernate to skip the autocommit check for local transactions since
* the underlying {@link javax.sql.DataSource} has already disabled autocommit.
*
* @since 5.2.10
*/
String SKIP_AUTOCOMMIT_CHECK ="hibernate.connection.skip_autocommit_check";
/** /**
* Names a prefix used to define arbitrary JDBC connection properties. These properties are passed along to * Names a prefix used to define arbitrary JDBC connection properties. These properties are passed along to
* the {@literal JDBC} provider when creating a connection. * the {@literal JDBC} provider when creating a connection.

View File

@ -62,11 +62,13 @@ public abstract class AbstractLogicalConnectionImplementor implements LogicalCon
@Override @Override
public void begin() { public void begin() {
try { try {
if ( !isSkipAutoCommitCheck() ) {
log.trace( "Preparing to begin transaction via JDBC Connection.setAutoCommit(false)" ); log.trace( "Preparing to begin transaction via JDBC Connection.setAutoCommit(false)" );
getConnectionForTransactionManagement().setAutoCommit( false ); getConnectionForTransactionManagement().setAutoCommit( false );
status = TransactionStatus.ACTIVE;
log.trace( "Transaction begun via JDBC Connection.setAutoCommit(false)" ); log.trace( "Transaction begun via JDBC Connection.setAutoCommit(false)" );
} }
status = TransactionStatus.ACTIVE;
}
catch( SQLException e ) { catch( SQLException e ) {
throw new TransactionException( "JDBC begin transaction failed: ", e ); throw new TransactionException( "JDBC begin transaction failed: ", e );
} }
@ -136,4 +138,8 @@ public abstract class AbstractLogicalConnectionImplementor implements LogicalCon
public TransactionStatus getStatus(){ public TransactionStatus getStatus(){
return status; return status;
} }
protected boolean isSkipAutoCommitCheck() {
return false;
}
} }

View File

@ -15,6 +15,9 @@ import java.sql.SQLException;
import org.hibernate.ConnectionAcquisitionMode; import org.hibernate.ConnectionAcquisitionMode;
import org.hibernate.ConnectionReleaseMode; import org.hibernate.ConnectionReleaseMode;
import org.hibernate.ResourceClosedException; import org.hibernate.ResourceClosedException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
@ -43,6 +46,8 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
private transient Connection physicalConnection; private transient Connection physicalConnection;
private boolean closed; private boolean closed;
private boolean skipAutoCommitCheck;
public LogicalConnectionManagedImpl( public LogicalConnectionManagedImpl(
JdbcConnectionAccess jdbcConnectionAccess, JdbcConnectionAccess jdbcConnectionAccess,
JdbcSessionContext jdbcSessionContext) { JdbcSessionContext jdbcSessionContext) {
@ -70,6 +75,15 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
if ( connectionHandlingMode.getAcquisitionMode() == ConnectionAcquisitionMode.IMMEDIATELY ) { if ( connectionHandlingMode.getAcquisitionMode() == ConnectionAcquisitionMode.IMMEDIATELY ) {
acquireConnectionIfNeeded(); acquireConnectionIfNeeded();
} }
ConfigurationService configurationService = jdbcSessionContext.getServiceRegistry()
.getService( ConfigurationService.class );
this.skipAutoCommitCheck = configurationService.getSetting(
AvailableSettings.SKIP_AUTOCOMMIT_CHECK,
StandardConverters.BOOLEAN,
false
);
} }
private PhysicalConnectionHandlingMode determineConnectionHandlingMode( private PhysicalConnectionHandlingMode determineConnectionHandlingMode(
@ -251,7 +265,8 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
@Override @Override
public void begin() { public void begin() {
initiallyAutoCommit = determineInitialAutoCommitMode( getConnectionForTransactionManagement() ); initiallyAutoCommit = !isSkipAutoCommitCheck() && determineInitialAutoCommitMode(
getConnectionForTransactionManagement() );
super.begin(); super.begin();
} }
@ -262,4 +277,9 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
resetConnection( initiallyAutoCommit ); resetConnection( initiallyAutoCommit );
initiallyAutoCommit = false; initiallyAutoCommit = false;
} }
@Override
protected boolean isSkipAutoCommitCheck() {
return skipAutoCommitCheck;
}
} }

View File

@ -4,10 +4,15 @@ import java.lang.reflect.Field;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.batch.spi.Batch; import org.hibernate.engine.jdbc.batch.spi.Batch;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.resource.jdbc.spi.JdbcObserver; import org.hibernate.resource.jdbc.spi.JdbcObserver;
import org.hibernate.resource.jdbc.spi.JdbcSessionContext; import org.hibernate.resource.jdbc.spi.JdbcSessionContext;
import org.hibernate.resource.jdbc.spi.JdbcSessionOwner; import org.hibernate.resource.jdbc.spi.JdbcSessionOwner;
@ -62,6 +67,12 @@ public class JdbcCoordinatorTest {
when( serviceRegistry.getService( eq( JdbcServices.class ) ) ).thenReturn( when( serviceRegistry.getService( eq( JdbcServices.class ) ) ).thenReturn(
jdbcServices ); jdbcServices );
ConfigurationService configurationService = Mockito.mock( ConfigurationService.class );
when( serviceRegistry.getService( eq( ConfigurationService.class ) ) ).thenReturn(
configurationService );
when( configurationService.getSetting(eq( AvailableSettings.SKIP_AUTOCOMMIT_CHECK ), same( StandardConverters.BOOLEAN), eq( false )) )
.thenReturn( false );
SqlExceptionHelper sqlExceptionHelper = Mockito.mock( SqlExceptionHelper.class ); SqlExceptionHelper sqlExceptionHelper = Mockito.mock( SqlExceptionHelper.class );
when( jdbcServices.getSqlExceptionHelper() ).thenReturn( when( jdbcServices.getSqlExceptionHelper() ).thenReturn(
sqlExceptionHelper ); sqlExceptionHelper );

View File

@ -44,7 +44,7 @@ import static org.junit.Assert.fail;
@RequiresDialect(H2Dialect.class) @RequiresDialect(H2Dialect.class)
public class CriteriaLiteralsTest extends BaseEntityManagerFunctionalTestCase { public class CriteriaLiteralsTest extends BaseEntityManagerFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); private PreparedStatementSpyConnectionProvider connectionProvider;
@Override @Override
protected Map getConfig() { protected Map getConfig() {
@ -56,6 +56,12 @@ public class CriteriaLiteralsTest extends BaseEntityManagerFunctionalTestCase {
return config; return config;
} }
@Override
public void buildEntityManagerFactory() throws Exception {
connectionProvider = new PreparedStatementSpyConnectionProvider();
super.buildEntityManagerFactory();
}
@Override @Override
public void releaseResources() { public void releaseResources() {
super.releaseResources(); super.releaseResources();

View File

@ -0,0 +1,135 @@
/*
* 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.test.resource.transaction.jdbc.autocommit;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.jdbc.PreparedStatementSpyConnectionProvider;
import org.hibernate.test.util.ReflectionUtil;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* @author Vlad Mihalcea
*/
public abstract class AbstractSkipAutoCommitTest extends BaseEntityManagerFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider =
new PreparedStatementSpyConnectionProvider() {
@Override
protected Connection actualConnection() throws SQLException {
Connection connection = super.actualConnection();
connection.setAutoCommit( false );
return connection;
}
};
@Override
protected Map getConfig() {
Map config = super.getConfig();
config.put( AvailableSettings.DATASOURCE, dataSource() );
config.put( AvailableSettings.SKIP_AUTOCOMMIT_CHECK, Boolean.TRUE );
config.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider );
return config;
}
protected abstract DataSource dataSource();
@Override
public void releaseResources() {
super.releaseResources();
connectionProvider.stop();
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
City.class,
};
}
@Test
public void test() {
connectionProvider.clear();
doInJPA( this::entityManagerFactory, entityManager -> {
City city = new City();
city.setId( 1L );
city.setName( "Cluj-Napoca" );
entityManager.persist( city );
assertTrue( connectionProvider.getAcquiredConnections().isEmpty() );
assertTrue( connectionProvider.getReleasedConnections().isEmpty() );
} );
verifyConnections();
connectionProvider.clear();
doInJPA( this::entityManagerFactory, entityManager -> {
City city = entityManager.find( City.class, 1L );
assertEquals( "Cluj-Napoca", city.getName() );
} );
verifyConnections();
}
private void verifyConnections() {
assertTrue( connectionProvider.getAcquiredConnections().isEmpty() );
List<Connection> connections = connectionProvider.getReleasedConnections();
assertEquals( 1, connections.size() );
Connection connection = connections.get( 0 );
try {
verify(connection, never()).setAutoCommit( false );
}
catch (SQLException e) {
fail(e.getMessage());
}
}
@Entity(name = "City" )
public static class City {
@Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.test.resource.transaction.jdbc.autocommit;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.test.util.ReflectionUtil;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(H2Dialect.class)
public class H2SkipAutoCommitTest extends AbstractSkipAutoCommitTest {
@Override
protected DataSource dataSource() {
DataSource dataSource = ReflectionUtil.newInstance( "org.h2.jdbcx.JdbcDataSource" );
ReflectionUtil.setProperty( dataSource, "URL", Environment.getProperties().getProperty( AvailableSettings.URL ) );
ReflectionUtil.setProperty( dataSource, "user", Environment.getProperties().getProperty( AvailableSettings.USER ) );
ReflectionUtil.setProperty( dataSource, "password", Environment.getProperties().getProperty( AvailableSettings.PASS ) );
return dataSource;
}
}

View File

@ -0,0 +1,34 @@
/*
* 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.test.resource.transaction.jdbc.autocommit;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.test.util.ReflectionUtil;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(MySQLDialect.class)
public class MySQLSkipAutoCommitTest extends AbstractSkipAutoCommitTest {
@Override
protected DataSource dataSource() {
DataSource dataSource = ReflectionUtil.newInstance( "com.mysql.cj.jdbc.MysqlDataSource" );
ReflectionUtil.setProperty( dataSource, "url", Environment.getProperties().getProperty( AvailableSettings.URL ) );
ReflectionUtil.setProperty( dataSource, "user", Environment.getProperties().getProperty( AvailableSettings.USER ) );
ReflectionUtil.setProperty( dataSource, "password", Environment.getProperties().getProperty( AvailableSettings.PASS ) );
return dataSource;
}
}

View File

@ -0,0 +1,34 @@
/*
* 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.test.resource.transaction.jdbc.autocommit;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.test.util.ReflectionUtil;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(Oracle8iDialect.class)
public class OracleSkipAutoCommitTest extends AbstractSkipAutoCommitTest {
@Override
protected DataSource dataSource() {
DataSource dataSource = ReflectionUtil.newInstance( "oracle.jdbc.pool.OracleDataSource" );
ReflectionUtil.setProperty( dataSource, "URL", Environment.getProperties().getProperty( AvailableSettings.URL ) );
ReflectionUtil.setProperty( dataSource, "user", Environment.getProperties().getProperty( AvailableSettings.USER ) );
ReflectionUtil.setProperty( dataSource, "password", Environment.getProperties().getProperty( AvailableSettings.PASS ) );
return dataSource;
}
}

View File

@ -0,0 +1,34 @@
/*
* 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.test.resource.transaction.jdbc.autocommit;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.test.util.ReflectionUtil;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(PostgreSQL81Dialect.class)
public class PostgreSQLSkipAutoCommitTest extends AbstractSkipAutoCommitTest {
@Override
protected DataSource dataSource() {
DataSource dataSource = ReflectionUtil.newInstance( "org.postgresql.ds.PGSimpleDataSource" );
ReflectionUtil.setProperty( dataSource, "url", Environment.getProperties().getProperty( AvailableSettings.URL ) );
ReflectionUtil.setProperty( dataSource, "user", Environment.getProperties().getProperty( AvailableSettings.USER ) );
ReflectionUtil.setProperty( dataSource, "password", Environment.getProperties().getProperty( AvailableSettings.PASS ) );
return dataSource;
}
}

View File

@ -0,0 +1,34 @@
/*
* 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.test.resource.transaction.jdbc.autocommit;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.test.util.ReflectionUtil;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(SQLServerDialect.class)
public class SQLServerSkipAutoCommitTest extends AbstractSkipAutoCommitTest {
@Override
protected DataSource dataSource() {
DataSource dataSource = ReflectionUtil.newInstance( "com.microsoft.sqlserver.jdbc.SQLServerDataSource" );
ReflectionUtil.setProperty( dataSource, "URL", Environment.getProperties().getProperty( AvailableSettings.URL ) );
ReflectionUtil.setProperty( dataSource, "user", Environment.getProperties().getProperty( AvailableSettings.USER ) );
ReflectionUtil.setProperty( dataSource, "password", Environment.getProperties().getProperty( AvailableSettings.PASS ) );
return dataSource;
}
}

View File

@ -3,6 +3,7 @@ package org.hibernate.test.util;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@ -62,6 +63,22 @@ public class ReflectionUtil {
} }
} }
/**
* Set target Object field to a certain value
* @param target Object whose field is being set
* @param fieldName Object field naem to set
* @param value the new value for the given field
*/
public static void setField(Object target, String fieldName, Object value) {
try {
Field field = getField(target.getClass(), fieldName);
field.set( target, value );
}
catch ( IllegalAccessException e ) {
throw new IllegalArgumentException("Field " + fieldName + " could not be set", e );
}
}
/** /**
* New target Object instance using the given arguments * New target Object instance using the given arguments
* @param constructorSupplier constructor supplier * @param constructorSupplier constructor supplier
@ -80,18 +97,63 @@ public class ReflectionUtil {
} }
/** /**
* Set target Object field to a certain value * New target Object instance using the given Class name
* @param target Object whose field is being set * @param className class name
* @param fieldName Object field naem to set * @return new Object instance
* @param value the new value for the given field
*/ */
public static void setField(Object target, String fieldName, Object value) { public static <T> T newInstance(String className) {
try { try {
Field field = getField(target.getClass(), fieldName); return (T) Class.forName( className ).newInstance();
field.set( target, value );
} }
catch ( IllegalAccessException e ) { catch ( ClassNotFoundException | IllegalAccessException | InstantiationException e ) {
throw new IllegalArgumentException("Field " + fieldName + " could not be set", e ); throw new IllegalArgumentException("Constructor could not be called", e );
}
}
/**
* Get setter method
*
* @param target target object
* @param property property
* @param parameterType setter parameter type
* @return setter method
*/
public static Method getSetter(Object target, String property, Class<?> parameterType) {
String setterMethodName = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
Method setter = getMethod(target, setterMethodName, parameterType);
setter.setAccessible(true);
return setter;
}
/**
* Get target method
*
* @param target target object
* @param methodName method name
* @param parameterTypes method parameter types
* @return return value
*/
public static Method getMethod(Object target, String methodName, Class... parameterTypes) {
try {
return target.getClass().getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Invoke setter method with the given parameter
*
* @param target target object
* @param property property
* @param parameter setter parameter
*/
public static void setProperty(Object target, String property, Object parameter) {
Method setter = getSetter( target, property, parameter.getClass());
try {
setter.invoke(target, parameter);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException(e);
} }
} }
} }

View File

@ -39,10 +39,22 @@ public class PreparedStatementSpyConnectionProvider
private final List<String> executeUpdateStatements = new ArrayList<>(); private final List<String> executeUpdateStatements = new ArrayList<>();
private final List<Connection> acquiredConnections = new ArrayList<>( ); private final List<Connection> acquiredConnections = new ArrayList<>( );
private final List<Connection> releasedConnections = new ArrayList<>( );
public PreparedStatementSpyConnectionProvider() {
}
public PreparedStatementSpyConnectionProvider(ConnectionProvider connectionProvider) {
super( connectionProvider );
}
protected Connection actualConnection() throws SQLException {
return super.getConnection();
}
@Override @Override
public Connection getConnection() throws SQLException { public Connection getConnection() throws SQLException {
Connection connection = spy( super.getConnection() ); Connection connection = spy( actualConnection() );
acquiredConnections.add( connection ); acquiredConnections.add( connection );
return connection; return connection;
} }
@ -50,9 +62,16 @@ public class PreparedStatementSpyConnectionProvider
@Override @Override
public void closeConnection(Connection conn) throws SQLException { public void closeConnection(Connection conn) throws SQLException {
acquiredConnections.remove( conn ); acquiredConnections.remove( conn );
releasedConnections.add( conn );
super.closeConnection( conn ); super.closeConnection( conn );
} }
@Override
public void stop() {
clear();
super.stop();
}
private Connection spy(Connection connection) { private Connection spy(Connection connection) {
if ( MockUtil.isMock( connection ) ) { if ( MockUtil.isMock( connection ) ) {
return connection; return connection;
@ -94,6 +113,7 @@ public class PreparedStatementSpyConnectionProvider
*/ */
public void clear() { public void clear() {
acquiredConnections.clear(); acquiredConnections.clear();
releasedConnections.clear();
preparedStatementMap.keySet().forEach( Mockito::reset ); preparedStatementMap.keySet().forEach( Mockito::reset );
preparedStatementMap.clear(); preparedStatementMap.clear();
} }
@ -167,4 +187,12 @@ public class PreparedStatementSpyConnectionProvider
public List<Connection> getAcquiredConnections() { public List<Connection> getAcquiredConnections() {
return acquiredConnections; return acquiredConnections;
} }
/**
* Get a list of current released Connections.
* @return list of current released Connections
*/
public List<Connection> getReleasedConnections() {
return releasedConnections;
}
} }

View File

@ -0,0 +1,120 @@
/*
* 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.test.resource.transaction.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.testing.jdbc.PreparedStatementSpyConnectionProvider;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* @author Vlad Mihalcea
*/
public class HikariCPSkipAutoCommitTest extends BaseCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider =
new PreparedStatementSpyConnectionProvider();
@Override
protected void configure(Configuration configuration) {
configuration.getProperties().put(
org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
configuration.getProperties().put( AvailableSettings.SKIP_AUTOCOMMIT_CHECK, Boolean.TRUE );
configuration.getProperties().put( "hibernate.hikari.autoCommit", Boolean.FALSE.toString() );
}
@Override
public void releaseSessionFactory() {
super.releaseSessionFactory();
connectionProvider.stop();
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
City.class,
};
}
@Test
public void test() {
connectionProvider.clear();
doInHibernate( this::sessionFactory, session -> {
City city = new City();
city.setId( 1L );
city.setName( "Cluj-Napoca" );
session.persist( city );
assertTrue( connectionProvider.getAcquiredConnections().isEmpty() );
assertTrue( connectionProvider.getReleasedConnections().isEmpty() );
} );
verifyConnections();
connectionProvider.clear();
doInHibernate( this::sessionFactory, session -> {
City city = session.find( City.class, 1L );
assertEquals( "Cluj-Napoca", city.getName() );
} );
verifyConnections();
}
private void verifyConnections() {
assertTrue( connectionProvider.getAcquiredConnections().isEmpty() );
List<Connection> connections = connectionProvider.getReleasedConnections();
assertEquals( 1, connections.size() );
Connection connection = connections.get( 0 );
try {
verify(connection, never()).setAutoCommit( false );
}
catch (SQLException e) {
fail(e.getMessage());
}
}
@Entity(name = "City" )
public static class City {
@Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -0,0 +1,60 @@
#
# 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>.
#
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
#log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (hibernateLoadPlanWalkPath->%X{hibernateLoadPlanWalkPath}) - %m%n
#log4j.appender.stdout-mdc=org.apache.log4j.ConsoleAppender
#log4j.appender.stdout-mdc.Target=System.out
#log4j.appender.stdout-mdc.layout=org.apache.log4j.PatternLayout
#log4j.appender.stdout-mdc.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (walk path -> %X{hibernateLoadPlanWalkPath}) - %m%n
log4j.appender.unclosedSessionFactoryFile=org.apache.log4j.FileAppender
log4j.appender.unclosedSessionFactoryFile.append=true
log4j.appender.unclosedSessionFactoryFile.file=target/tmp/log/UnclosedSessionFactoryWarnings.log
log4j.appender.unclosedSessionFactoryFile.layout=org.apache.log4j.PatternLayout
log4j.appender.unclosedSessionFactoryFile.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.rootLogger=info, stdout
#log4j.logger.org.hibernate.loader.plan=trace, stdout-mdc
#log4j.additivity.org.hibernate.loader.plan=false
#log4j.logger.org.hibernate.persister.walking=trace, stdout-mdc
#log4j.additivity.org.hibernate.persister.walking=false
log4j.logger.org.hibernate.tool.hbm2ddl=trace
log4j.logger.org.hibernate.testing.cache=debug
# SQL Logging - HHH-6833
log4j.logger.org.hibernate.SQL=debug
log4j.logger.org.hibernate.type.descriptor.sql.BasicBinder=trace
log4j.logger.org.hibernate.type.descriptor.sql.BasicExtractor=trace
log4j.logger.org.hibernate.hql.internal.ast=debug
log4j.logger.org.hibernate.sql.ordering.antlr=debug
log4j.logger.org.hibernate.loader.plan2.build.internal.LoadPlanImpl=debug
log4j.logger.org.hibernate.loader.plan2.build.spi.LoadPlanTreePrinter=debug
log4j.logger.org.hibernate.loader.plan2.exec.spi.EntityLoadQueryDetails=debug
log4j.logger.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=info
log4j.logger.org.hibernate.boot.model.source.internal.hbm.ModelBinder=debug
log4j.logger.org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry=debug
### When entity copy merge functionality is enabled using:
### hibernate.event.merge.entity_copy_observer=log, the following will
### provide information about merged entity copies.
### log4j.logger.org.hibernate.event.internal.EntityCopyAllowedLoggedObserver=debug
log4j.logger.org.hibernate.testing.junit4.TestClassMetadata=info, unclosedSessionFactoryFile
log4j.logger.org.hibernate.boot.model.process.internal.ScanningCoordinator=debug

View File

@ -35,6 +35,13 @@ public class ConnectionProviderDelegate implements
private ConnectionProvider connectionProvider; private ConnectionProvider connectionProvider;
public ConnectionProviderDelegate() {
}
public ConnectionProviderDelegate(ConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
}
@Override @Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) { public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry; this.serviceRegistry = serviceRegistry;
@ -42,6 +49,7 @@ public class ConnectionProviderDelegate implements
@Override @Override
public void configure(Map configurationValues) { public void configure(Map configurationValues) {
if ( connectionProvider == null ) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> settings = new HashMap<>( configurationValues ); Map<String, Object> settings = new HashMap<>( configurationValues );
settings.remove( AvailableSettings.CONNECTION_PROVIDER ); settings.remove( AvailableSettings.CONNECTION_PROVIDER );
@ -54,6 +62,7 @@ public class ConnectionProviderDelegate implements
configurableConnectionProvider.configure( settings ); configurableConnectionProvider.configure( settings );
} }
} }
}
@Override @Override
public Connection getConnection() throws SQLException { public Connection getConnection() throws SQLException {