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

This commit is contained in:
Steve Ebersole 2017-04-13 13:06:54 -05:00 committed by Andrea Boriero
parent ca103dcb28
commit db528238d8
16 changed files with 115 additions and 35 deletions

View File

@ -53,14 +53,34 @@ Note that for backwards compatibility, if a https://docs.jboss.org/hibernate/orm
|`hibernate.connection.password` or `javax.persistence.jdbc.password` | | Names the JDBC connection password.
|`hibernate.connection.isolation` | `REPEATABLE_READ` or
`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.skip_autocommit_check` | `true` or `false` (default value) |
|`hibernate.connection.autocommit` | `true` or `false` (default value) | Names the initial autocommit mode for JDBC Connections returned from a connection pool created in certain ConnectionProvider impl. See discussion of `hibernate.transaction.skip_setautocommit` as well.
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.
|`hibernate.connection.provider_disables_autocommit` | `true` or `false` (default value) |
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.
Indicates a promise by the user that Connections that Hibernate obtains from the configured ConnectionProvider
have auto-commit disabled when they are obtained from that provider, whether that provider is backed by
a DataSource or some other Connection pooling mechanism. Generally this occurs when:
* Hibernate is configured to get Connections from an underlying DataSource, and that DataSource is already configured to disable auto-commit on its managed Connections
* Hibernate is configured to get Connections from a non-DataSource connection pool and that connection pool is already configured to disable auto-commit. For the
Hibernate provided impls this will depend on the value of {@link #AUTOCOMMIT} setting.
Hibernate uses this assurance as an opportunity to opt-out of certain operations that may have a performance
impact (although this impact is general negligible). Specifically, when a transaction is started via the
Hibernate or JPA transaction APIs Hibernate will generally immediately acquire a Connection from the
provider and:
* check whether the Connection is initially in auto-commit mode via a call to `Connection#getAutocommit` to know how to clean up the Connection when released.
* start a JDBC transaction by calling `Connection#setAutocommit(false)`
We can skip both of those steps if we know that the ConnectionProvider will always return Connections with auto-commit disabled.
That is the purpose of this setting. 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.
Please note however that it is inappropriate to set this value to `true` when the Connections Hibernate gets
from the provider do not in fact have auto-commit disabled - doing so will lead to Hibernate executing SQL operations
outside of any JDBC/SQL transaction.
|`hibernate.connection.datasource` | |
@ -529,6 +549,7 @@ The ability to handle this situation requires checking the Thread ID every time
|`hibernate.transaction.factory_class` | | This is a legacy setting that's been deprecated and you should use the `hibernate.transaction.jta.platform` instead.
|===================================================================================================================================================================================================================================
[[configurations-multi-tenancy]]

View File

@ -663,6 +663,11 @@ public interface SessionFactoryBuilder {
@Deprecated
SessionFactoryBuilder applyConnectionReleaseMode(ConnectionReleaseMode connectionReleaseMode);
/**
* @see org.hibernate.cfg.AvailableSettings#CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT
*/
SessionFactoryBuilder applyConnectionProviderDisablesAutoCommit(boolean providerDisablesAutoCommit);
/**
* Should Hibernate apply comments to SQL it generates?
*

View File

@ -44,6 +44,7 @@ import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.engine.config.internal.ConfigurationServiceImpl;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
@ -406,6 +407,12 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return this;
}
@Override
public SessionFactoryBuilder applyConnectionProviderDisablesAutoCommit(boolean providerDisablesAutoCommit) {
this.options.connectionProviderDisablesAutoCommit = providerDisablesAutoCommit;
return this;
}
@Override
public SessionFactoryBuilder applySqlComments(boolean enabled) {
this.options.commentsEnabled = enabled;
@ -553,6 +560,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
private boolean scrollableResultSetsEnabled;
private boolean commentsEnabled;
private PhysicalConnectionHandlingMode connectionHandlingMode;
private boolean connectionProviderDisablesAutoCommit;
private boolean wrapResultSetsEnabled;
private TimeZone jdbcTimeZone;
private boolean queryParametersValidationEnabled;
@ -725,6 +733,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
this.jdbcFetchSize = ConfigurationHelper.getInteger( STATEMENT_FETCH_SIZE, configurationSettings );
this.connectionHandlingMode = interpretConnectionHandlingMode( configurationSettings, serviceRegistry );
this.connectionProviderDisablesAutoCommit = ConfigurationHelper.getBoolean(
AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT,
configurationSettings,
false
);
this.commentsEnabled = ConfigurationHelper.getBoolean( USE_SQL_COMMENTS, configurationSettings );
@ -1155,6 +1168,10 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return connectionHandlingMode;
}
public boolean connectionProviderDisablesAutoCommit() {
return connectionProviderDisablesAutoCommit;
}
@Override
public ConnectionReleaseMode getConnectionReleaseMode() {
return getPhysicalConnectionHandlingMode().getReleaseMode();
@ -1478,6 +1495,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return options.getPhysicalConnectionHandlingMode();
}
@Override
public boolean connectionProviderDisablesAutoCommit() {
return options.connectionProviderDisablesAutoCommit();
}
@Override
public ConnectionReleaseMode getConnectionReleaseMode() {
return getPhysicalConnectionHandlingMode().getReleaseMode();

View File

@ -121,6 +121,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
private final boolean scrollableResultSetsEnabled;
private final boolean commentsEnabled;
private final PhysicalConnectionHandlingMode physicalConnectionHandlingMode;
private final boolean connectionProviderDisablesAutoCommit;
private final boolean wrapResultSetsEnabled;
private final TimeZone jdbcTimeZone;
@ -192,6 +193,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
this.schemaAutoTooling = state.getSchemaAutoTooling();
this.physicalConnectionHandlingMode = state.getPhysicalConnectionHandlingMode();
this.connectionProviderDisablesAutoCommit = state.connectionProviderDisablesAutoCommit();
this.getGeneratedKeysEnabled = state.isGetGeneratedKeysEnabled();
this.jdbcBatchSize = state.getJdbcBatchSize();
this.jdbcBatchVersionedData = state.isJdbcBatchVersionedData();
@ -490,6 +492,11 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
return physicalConnectionHandlingMode.getReleaseMode();
}
@Override
public boolean doesConnectionProviderDisableAutoCommit() {
return connectionProviderDisablesAutoCommit;
}
@Override
public boolean isCommentsEnabled() {
return commentsEnabled;

View File

@ -155,6 +155,8 @@ public interface SessionFactoryOptionsState {
PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode();
boolean connectionProviderDisablesAutoCommit();
/**
* @deprecated Use {@link #getPhysicalConnectionHandlingMode()} instead
*/

View File

@ -348,6 +348,12 @@ public abstract class AbstractDelegatingSessionFactoryBuilder<T extends Abstract
return getThis();
}
@Override
public SessionFactoryBuilder applyConnectionProviderDisablesAutoCommit(boolean providerDisablesAutoCommit) {
delegate.applyConnectionProviderDisablesAutoCommit( providerDisablesAutoCommit );
return getThis();
}
@Override
public T applySqlComments(boolean enabled) {
delegate.applySqlComments( enabled );

View File

@ -319,6 +319,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
return delegate.getPhysicalConnectionHandlingMode();
}
@Override
public boolean doesConnectionProviderDisableAutoCommit() {
return delegate.doesConnectionProviderDisableAutoCommit();
}
@Override
@SuppressWarnings("deprecation")
public ConnectionReleaseMode getConnectionReleaseMode() {

View File

@ -181,6 +181,8 @@ public interface SessionFactoryOptions {
PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode();
boolean doesConnectionProviderDisableAutoCommit();
/**
* @deprecated Use {@link #getPhysicalConnectionHandlingMode()} instead
*/

View File

@ -324,9 +324,12 @@ public interface AvailableSettings {
String ISOLATION ="hibernate.connection.isolation";
/**
* Names the {@literal JDBC} autocommit mode
* Controls the autocommit mode of {@literal JDBC} Connections obtained
* from a non-DataSource ConnectionProvider - assuming the ConnectionProvider
* impl properly leverages this setting (the provided Hibernate impls all
* do).
*/
String AUTOCOMMIT ="hibernate.connection.autocommit";
String AUTOCOMMIT = "hibernate.connection.autocommit";
/**
* Maximum number of inactive connections for the built-in Hibernate connection pool.
@ -343,12 +346,16 @@ public interface AvailableSettings {
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.
* Allows a user to tell Hibernate that the Connections we obtain from the configured
* ConnectionProvider will already have auto-commit disabled when we acquire them from
* the provider. When we get connections already in auto-commit, this allows us to circumvent
* some operations in the interest of performance.
* <p/>
* Default value is {@code false} - do not skip, aka call setAutocommit
*
* @since 5.2.10
*/
String SKIP_AUTOCOMMIT_CHECK ="hibernate.connection.skip_autocommit_check";
String CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT= "hibernate.connection.provider_disables_autocommit";
/**
* Names a prefix used to define arbitrary JDBC connection properties. These properties are passed along to

View File

@ -60,6 +60,11 @@ public class JdbcSessionContextImpl implements JdbcSessionContext {
return connectionHandlingMode;
}
@Override
public boolean doesConnectionProviderDisableAutoCommit() {
return settings().doesConnectionProviderDisableAutoCommit();
}
@Override
public ConnectionReleaseMode getConnectionReleaseMode() {
return connectionHandlingMode.getReleaseMode();

View File

@ -62,7 +62,7 @@ public abstract class AbstractLogicalConnectionImplementor implements LogicalCon
@Override
public void begin() {
try {
if ( !isSkipAutoCommitCheck() ) {
if ( !doConnectionsFromProviderHaveAutoCommitDisabled() ) {
log.trace( "Preparing to begin transaction via JDBC Connection.setAutoCommit(false)" );
getConnectionForTransactionManagement().setAutoCommit( false );
log.trace( "Transaction begun via JDBC Connection.setAutoCommit(false)" );
@ -139,7 +139,7 @@ public abstract class AbstractLogicalConnectionImplementor implements LogicalCon
return status;
}
protected boolean isSkipAutoCommitCheck() {
protected boolean doConnectionsFromProviderHaveAutoCommitDisabled() {
return false;
}
}

View File

@ -46,7 +46,7 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
private transient Connection physicalConnection;
private boolean closed;
private boolean skipAutoCommitCheck;
private boolean providerDisablesAutoCommit;
public LogicalConnectionManagedImpl(
JdbcConnectionAccess jdbcConnectionAccess,
@ -76,14 +76,16 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
acquireConnectionIfNeeded();
}
ConfigurationService configurationService = jdbcSessionContext.getServiceRegistry()
.getService( ConfigurationService.class );
this.skipAutoCommitCheck = configurationService.getSetting(
AvailableSettings.SKIP_AUTOCOMMIT_CHECK,
StandardConverters.BOOLEAN,
false
);
this.providerDisablesAutoCommit = jdbcSessionContext.doesConnectionProviderDisableAutoCommit();
if ( providerDisablesAutoCommit ) {
log.debug(
"`hibernate.connection.provider_disables_autocommit` was enabled. This setting should only be " +
"enabled when you are certain that the Connections given to Hibernate by the " +
"ConnectionProvider have auto-commit disabled. Enabling this setting when the " +
"Connections do not have auto-commit disabled will lead to Hibernate executing " +
"SQL operations outside of any JDBC/SQL transaction."
);
}
}
private PhysicalConnectionHandlingMode determineConnectionHandlingMode(
@ -265,7 +267,7 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
@Override
public void begin() {
initiallyAutoCommit = !isSkipAutoCommitCheck() && determineInitialAutoCommitMode(
initiallyAutoCommit = !doConnectionsFromProviderHaveAutoCommitDisabled() && determineInitialAutoCommitMode(
getConnectionForTransactionManagement() );
super.begin();
}
@ -279,7 +281,7 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
}
@Override
protected boolean isSkipAutoCommitCheck() {
return skipAutoCommitCheck;
protected boolean doConnectionsFromProviderHaveAutoCommitDisabled() {
return providerDisablesAutoCommit;
}
}

View File

@ -23,6 +23,8 @@ public interface JdbcSessionContext {
PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode();
boolean doesConnectionProviderDisableAutoCommit();
/**
* @deprecated Use {@link #getPhysicalConnectionHandlingMode} instead
*/

View File

@ -4,7 +4,6 @@ import java.lang.reflect.Field;
import java.sql.Connection;
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;
@ -12,7 +11,6 @@ import org.hibernate.engine.jdbc.batch.spi.Batch;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
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.JdbcSessionContext;
import org.hibernate.resource.jdbc.spi.JdbcSessionOwner;
@ -70,7 +68,7 @@ public class JdbcCoordinatorTest {
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 )) )
when( configurationService.getSetting( eq( AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT ), same( StandardConverters.BOOLEAN), eq( false )) )
.thenReturn( false );
SqlExceptionHelper sqlExceptionHelper = Mockito.mock( SqlExceptionHelper.class );

View File

@ -15,13 +15,9 @@ 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;
@ -51,7 +47,7 @@ public abstract class AbstractSkipAutoCommitTest extends BaseEntityManagerFuncti
Map config = super.getConfig();
config.put( AvailableSettings.DATASOURCE, dataSource() );
config.put( AvailableSettings.SKIP_AUTOCOMMIT_CHECK, Boolean.TRUE );
config.put( AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT, Boolean.TRUE );
config.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider );
return config;

View File

@ -4,7 +4,7 @@
* 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;
package org.hibernate.test.hikaricp;
import java.sql.Connection;
import java.sql.SQLException;
@ -40,7 +40,7 @@ public class HikariCPSkipAutoCommitTest extends BaseCoreFunctionalTestCase {
org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER,
connectionProvider
);
configuration.getProperties().put( AvailableSettings.SKIP_AUTOCOMMIT_CHECK, Boolean.TRUE );
configuration.getProperties().put( AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT, Boolean.TRUE );
configuration.getProperties().put( "hibernate.hikari.autoCommit", Boolean.FALSE.toString() );
}