From db528238d8525be1a83b9f9f20396bf91d5f0dbd Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 13 Apr 2017 13:06:54 -0500 Subject: [PATCH] HHH-11542 - Allow the auto-commit resolution to be configurable for RESOURCE_LOCAL transactions --- .../userguide/appendices/Configurations.adoc | 33 +++++++++++++++---- .../hibernate/boot/SessionFactoryBuilder.java | 5 +++ .../internal/SessionFactoryBuilderImpl.java | 22 +++++++++++++ .../internal/SessionFactoryOptionsImpl.java | 7 ++++ .../internal/SessionFactoryOptionsState.java | 2 ++ ...stractDelegatingSessionFactoryBuilder.java | 6 ++++ ...stractDelegatingSessionFactoryOptions.java | 5 +++ .../boot/spi/SessionFactoryOptions.java | 2 ++ .../org/hibernate/cfg/AvailableSettings.java | 17 +++++++--- .../internal/JdbcSessionContextImpl.java | 5 +++ .../AbstractLogicalConnectionImplementor.java | 4 +-- .../LogicalConnectionManagedImpl.java | 26 ++++++++------- .../resource/jdbc/spi/JdbcSessionContext.java | 2 ++ .../jdbc/internal/JdbcCoordinatorTest.java | 4 +-- .../AbstractSkipAutoCommitTest.java | 6 +--- .../hikaricp/HikariCPSkipAutoCommitTest.java | 4 +-- 16 files changed, 115 insertions(+), 35 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 4f16b157ff..55322866c0 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -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]] diff --git a/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java index 4a112466f9..ebf7f16b93 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java @@ -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? * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java index f9cb5f7e5d..a9ee66a01f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java @@ -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(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java index bf93c5da54..55e0505f6b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java @@ -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; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java index 25c12cccc4..a1158c811c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java @@ -155,6 +155,8 @@ public interface SessionFactoryOptionsState { PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode(); + boolean connectionProviderDisablesAutoCommit(); + /** * @deprecated Use {@link #getPhysicalConnectionHandlingMode()} instead */ diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java index fa125f9f52..4b662a5877 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java @@ -348,6 +348,12 @@ public abstract class AbstractDelegatingSessionFactoryBuilder + * 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 diff --git a/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java index 0af612615f..da6c9f3ca7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java @@ -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(); diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java index 86b1b0bd70..0af3f3108f 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java @@ -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; } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java index d6242c8e41..96517fc45a 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java @@ -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; } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java index 4abb6726a9..8116dd04ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java @@ -23,6 +23,8 @@ public interface JdbcSessionContext { PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode(); + boolean doesConnectionProviderDisableAutoCommit(); + /** * @deprecated Use {@link #getPhysicalConnectionHandlingMode} instead */ diff --git a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java index 8832f662e3..df1646aa58 100644 --- a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java @@ -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 ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java index 1e1e71e816..25d256e136 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java @@ -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; diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java index a3cfc2dbaa..6e2cd63875 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java @@ -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 . */ -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() ); }