diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java index 8269ccf73c..fb7bc9a174 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -30,6 +31,9 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.jdbc.SchemaManagementProvider; +import org.springframework.boot.jdbc.metadata.CompositeDataSourcePoolMetadataProvider; +import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadata; +import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider; import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; import org.springframework.context.annotation.Configuration; import org.springframework.jndi.JndiLocatorDelegate; @@ -56,6 +60,8 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform"; + private static final String PROVIDER_DISABLES_AUTOCOMMIT = "hibernate.connection.provider_disables_autocommit"; + /** * {@code NoJtaPlatform} implementations for various Hibernate versions. */ @@ -73,14 +79,19 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { private final HibernateDefaultDdlAutoProvider defaultDdlAutoProvider; + private DataSourcePoolMetadataProvider poolMetadataProvider; + HibernateJpaConfiguration(DataSource dataSource, JpaProperties jpaProperties, ObjectProvider jtaTransactionManager, ObjectProvider transactionManagerCustomizers, + ObjectProvider> metadataProviders, ObjectProvider> providers) { super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers); this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider( providers.getIfAvailable(Collections::emptyList)); + this.poolMetadataProvider = new CompositeDataSourcePoolMetadataProvider( + metadataProviders.getIfAvailable()); } @Override @@ -103,6 +114,9 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { if (!vendorProperties.containsKey(JTA_PLATFORM)) { configureJtaPlatform(vendorProperties); } + if (!vendorProperties.containsKey(PROVIDER_DISABLES_AUTOCOMMIT)) { + configureProviderDisablesAutocommit(vendorProperties); + } } private void configureJtaPlatform(Map vendorProperties) @@ -124,6 +138,19 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { } } + private void configureProviderDisablesAutocommit(Map vendorProperties) { + if (isDataSourceAutoCommitDisabled() && !isJta()) { + vendorProperties.put(PROVIDER_DISABLES_AUTOCOMMIT, "true"); + } + } + + private boolean isDataSourceAutoCommitDisabled() { + DataSourcePoolMetadata poolMetadata = this.poolMetadataProvider + .getDataSourcePoolMetadata(getDataSource()); + return poolMetadata != null + && Boolean.FALSE.equals(poolMetadata.getDefaultAutoCommit()); + } + private boolean runningOnWebSphere() { return ClassUtils.isPresent( "com.ibm.websphere.jtaextensions." + "ExtendedJTATransaction", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index c91b76a3be..18f0fd44b0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -33,6 +33,7 @@ import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; +import com.zaxxer.hikari.HikariDataSource; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.junit.Test; @@ -52,6 +53,7 @@ import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.mockito.Mockito.mock; /** @@ -75,12 +77,12 @@ public class HibernateJpaAutoConfigurationTests contextRunner().withPropertyValues("spring.datasource.data:classpath:/city.sql", // Missing: "spring.datasource.schema:classpath:/ddl.sql").run((context) -> { - assertThat(context).hasFailed(); - assertThat(context.getStartupFailure()) - .hasMessageContaining("ddl.sql"); - assertThat(context.getStartupFailure()) - .hasMessageContaining("spring.datasource.schema"); - }); + assertThat(context).hasFailed(); + assertThat(context.getStartupFailure()) + .hasMessageContaining("ddl.sql"); + assertThat(context.getStartupFailure()) + .hasMessageContaining("spring.datasource.schema"); + }); } @Test @@ -105,7 +107,7 @@ public class HibernateJpaAutoConfigurationTests "spring.datasource.data:classpath:/city.sql") .run((context) -> assertThat( context.getBean(TestInitializedJpaConfiguration.class).called) - .isTrue()); + .isTrue()); } @Test @@ -164,7 +166,7 @@ public class HibernateJpaAutoConfigurationTests .getJpaPropertyMap(); assertThat((String) jpaPropertyMap .get("hibernate.transaction.jta.platform")) - .isEqualTo(TestJtaPlatform.class.getName()); + .isEqualTo(TestJtaPlatform.class.getName()); }); } @@ -194,6 +196,62 @@ public class HibernateJpaAutoConfigurationTests }); } + @Test + public void providerDisablesAutoCommitIsConfigured() { + contextRunner().withPropertyValues( + "spring.datasource.type:" + HikariDataSource.class.getName(), + "spring.datasource.hikari.auto-commit:false").run((context) -> { + Map jpaProperties = context + .getBean(LocalContainerEntityManagerFactoryBean.class) + .getJpaPropertyMap(); + assertThat(jpaProperties).contains(entry( + "hibernate.connection.provider_disables_autocommit", "true")); + }); + } + + @Test + public void providerDisablesAutoCommitIsNotConfiguredIfAutoCommitIsEnabled() { + contextRunner().withPropertyValues( + "spring.datasource.type:" + HikariDataSource.class.getName(), + "spring.datasource.hikari.auto-commit:true").run((context) -> { + Map jpaProperties = context + .getBean(LocalContainerEntityManagerFactoryBean.class) + .getJpaPropertyMap(); + assertThat(jpaProperties).doesNotContainKeys( + "hibernate.connection.provider_disables_autocommit"); + }); + } + + @Test + public void providerDisablesAutoCommitIsNotConfiguredIfPropertyIsSet() { + contextRunner().withPropertyValues( + "spring.datasource.type:" + HikariDataSource.class.getName(), + "spring.datasource.hikari.auto-commit:false", + "spring.jpa.properties.hibernate.connection.provider_disables_autocommit=false" + ).run((context) -> { + Map jpaProperties = context + .getBean(LocalContainerEntityManagerFactoryBean.class) + .getJpaPropertyMap(); + assertThat(jpaProperties).contains(entry( + "hibernate.connection.provider_disables_autocommit", "false")); + }); + } + + @Test + public void providerDisablesAutoCommitIsNotConfiguredWihJta() { + contextRunner() + .withConfiguration(AutoConfigurations.of(JtaAutoConfiguration.class)) + .withPropertyValues( + "spring.datasource.type:" + HikariDataSource.class.getName(), + "spring.datasource.hikari.auto-commit:false").run((context) -> { + Map jpaProperties = context + .getBean(LocalContainerEntityManagerFactoryBean.class) + .getJpaPropertyMap(); + assertThat(jpaProperties).doesNotContainKeys( + "hibernate.connection.provider_disables_autocommit"); + }); + } + @Configuration @TestAutoConfigurationPackage(City.class) static class TestInitializedJpaConfiguration { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/CommonsDbcp2DataSourcePoolMetadata.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/CommonsDbcp2DataSourcePoolMetadata.java index 58e5e29c96..80ad218448 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/CommonsDbcp2DataSourcePoolMetadata.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/CommonsDbcp2DataSourcePoolMetadata.java @@ -53,4 +53,9 @@ public class CommonsDbcp2DataSourcePoolMetadata return getDataSource().getValidationQuery(); } + @Override + public Boolean getDefaultAutoCommit() { + return getDataSource().getDefaultAutoCommit(); + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadata.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadata.java index 635cc6b87d..112b58fd07 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadata.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadata.java @@ -71,4 +71,12 @@ public interface DataSourcePoolMetadata { */ String getValidationQuery(); + /** + * The default auto-commit state of connections created by this pool. + * If not set ({@code null}), default is JDBC driver default + * (If set to null then the java.sql.Connection.setAutoCommit(boolean) method will not be called.) + * @return the default auto-commit state or {@code null} + */ + Boolean getDefaultAutoCommit(); + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadata.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadata.java index 251999aa5f..700691e8a8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadata.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadata.java @@ -66,4 +66,9 @@ public class HikariDataSourcePoolMetadata return getDataSource().getConnectionTestQuery(); } + @Override + public Boolean getDefaultAutoCommit() { + return getDataSource().isAutoCommit(); + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/TomcatDataSourcePoolMetadata.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/TomcatDataSourcePoolMetadata.java index 6e495bbe61..f4b64408c9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/TomcatDataSourcePoolMetadata.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/TomcatDataSourcePoolMetadata.java @@ -53,4 +53,9 @@ public class TomcatDataSourcePoolMetadata return getDataSource().getValidationQuery(); } + @Override + public Boolean getDefaultAutoCommit() { + return getDataSource().isDefaultAutoCommit(); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadataTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadataTests.java index 17c6ce1845..e401cb349f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadataTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadataTests.java @@ -86,6 +86,9 @@ public abstract class AbstractDataSourcePoolMetadataTests initializeBuilder() { return DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver") .url("jdbc:hsqldb:mem:test").username("sa"); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/CommonsDbcp2DataSourcePoolMetadataTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/CommonsDbcp2DataSourcePoolMetadataTests.java index 2cc7816277..ec28aa4a59 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/CommonsDbcp2DataSourcePoolMetadataTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/CommonsDbcp2DataSourcePoolMetadataTests.java @@ -81,6 +81,14 @@ public class CommonsDbcp2DataSourcePoolMetadataTests .isEqualTo("SELECT FROM FOO"); } + @Override + public void getDefaultAutoCommit() { + BasicDataSource dataSource = createDataSource(); + dataSource.setDefaultAutoCommit(false); + assertThat(new CommonsDbcp2DataSourcePoolMetadata(dataSource) + .getDefaultAutoCommit()).isFalse(); + } + private CommonsDbcp2DataSourcePoolMetadata createDataSourceMetadata(int minSize, int maxSize) { BasicDataSource dataSource = createDataSource(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadataTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadataTests.java index 45921713d5..4b8462def3 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadataTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadataTests.java @@ -50,6 +50,14 @@ public class HikariDataSourcePoolMetadataTests .isEqualTo("SELECT FROM FOO"); } + @Override + public void getDefaultAutoCommit() { + HikariDataSource dataSource = createDataSource(0, 4); + dataSource.setAutoCommit(false); + assertThat(new HikariDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()) + .isFalse(); + } + private HikariDataSource createDataSource(int minSize, int maxSize) { HikariDataSource dataSource = initializeBuilder().type(HikariDataSource.class) .build(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/TomcatDataSourcePoolMetadataTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/TomcatDataSourcePoolMetadataTests.java index c013556da1..403f70a9ea 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/TomcatDataSourcePoolMetadataTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/TomcatDataSourcePoolMetadataTests.java @@ -50,6 +50,14 @@ public class TomcatDataSourcePoolMetadataTests .isEqualTo("SELECT FROM FOO"); } + @Override + public void getDefaultAutoCommit() { + DataSource dataSource = createDataSource(0, 4); + dataSource.setDefaultAutoCommit(false); + assertThat(new TomcatDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()) + .isFalse(); + } + private DataSource createDataSource(int minSize, int maxSize) { DataSource dataSource = initializeBuilder().type(DataSource.class).build(); dataSource.setMinIdle(minSize);