diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java index 1f32bf7aeb..dcade76a57 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java @@ -17,18 +17,9 @@ package org.springframework.boot.autoconfigure.orm.jpa; import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; import javax.persistence.EntityManager; -import javax.sql.DataSource; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionMessage; @@ -38,20 +29,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.HibernateEntityManagerCondition; -import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; -import org.springframework.boot.jdbc.SchemaManagementProvider; -import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.jndi.JndiLocatorDelegate; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; -import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.util.ClassUtils; /** @@ -65,143 +51,10 @@ import org.springframework.util.ClassUtils; @Configuration @ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class }) @Conditional(HibernateEntityManagerCondition.class) +@EnableConfigurationProperties(JpaProperties.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class }) -public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { - - private static final Log logger = LogFactory - .getLog(HibernateJpaAutoConfiguration.class); - - private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform"; - - /** - * {@code NoJtaPlatform} implementations for various Hibernate versions. - */ - private static final String[] NO_JTA_PLATFORM_CLASSES = { - "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform", - "org.hibernate.service.jta.platform.internal.NoJtaPlatform" }; - - /** - * {@code WebSphereExtendedJtaPlatform} implementations for various Hibernate - * versions. - */ - private static final String[] WEBSPHERE_JTA_PLATFORM_CLASSES = { - "org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform", - "org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform", }; - - private final HibernateDefaultDdlAutoProvider defaultDdlAutoProvider; - - public HibernateJpaAutoConfiguration(DataSource dataSource, - JpaProperties jpaProperties, - ObjectProvider jtaTransactionManager, - ObjectProvider transactionManagerCustomizers, - ObjectProvider> providers) { - super(dataSource, jpaProperties, jtaTransactionManager, - transactionManagerCustomizers); - this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider( - providers.getIfAvailable(Collections::emptyList)); - } - - @Override - protected AbstractJpaVendorAdapter createJpaVendorAdapter() { - return new HibernateJpaVendorAdapter(); - } - - @Override - protected Map getVendorProperties() { - Map vendorProperties = new LinkedHashMap<>(); - String defaultDdlMode = this.defaultDdlAutoProvider - .getDefaultDdlAuto(getDataSource()); - vendorProperties.putAll(getProperties().getHibernateProperties(defaultDdlMode)); - return vendorProperties; - } - - @Override - protected void customizeVendorProperties(Map vendorProperties) { - super.customizeVendorProperties(vendorProperties); - if (!vendorProperties.containsKey(JTA_PLATFORM)) { - configureJtaPlatform(vendorProperties); - } - } - - private void configureJtaPlatform(Map vendorProperties) - throws LinkageError { - JtaTransactionManager jtaTransactionManager = getJtaTransactionManager(); - if (jtaTransactionManager != null) { - if (runningOnWebSphere()) { - // We can never use SpringJtaPlatform on WebSphere as - // WebSphereUowTransactionManager has a null TransactionManager - // which will cause Hibernate to NPE - configureWebSphereTransactionPlatform(vendorProperties); - } - else { - configureSpringJtaPlatform(vendorProperties, jtaTransactionManager); - } - } - else { - vendorProperties.put(JTA_PLATFORM, getNoJtaPlatformManager()); - } - } - - private boolean runningOnWebSphere() { - return ClassUtils.isPresent( - "com.ibm.websphere.jtaextensions." + "ExtendedJTATransaction", - getClass().getClassLoader()); - } - - private void configureWebSphereTransactionPlatform( - Map vendorProperties) { - vendorProperties.put(JTA_PLATFORM, getWebSphereJtaPlatformManager()); - } - - private Object getWebSphereJtaPlatformManager() { - return getJtaPlatformManager(WEBSPHERE_JTA_PLATFORM_CLASSES); - } - - private void configureSpringJtaPlatform(Map vendorProperties, - JtaTransactionManager jtaTransactionManager) { - try { - vendorProperties.put(JTA_PLATFORM, - new SpringJtaPlatform(jtaTransactionManager)); - } - catch (LinkageError ex) { - // NoClassDefFoundError can happen if Hibernate 4.2 is used and some - // containers (e.g. JBoss EAP 6) wraps it in the superclass LinkageError - if (!isUsingJndi()) { - throw new IllegalStateException("Unable to set Hibernate JTA " - + "platform, are you using the correct " - + "version of Hibernate?", ex); - } - // Assume that Hibernate will use JNDI - if (logger.isDebugEnabled()) { - logger.debug("Unable to set Hibernate JTA platform : " + ex.getMessage()); - } - } - } - - private boolean isUsingJndi() { - try { - return JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable(); - } - catch (Error ex) { - return false; - } - } - - private Object getNoJtaPlatformManager() { - return getJtaPlatformManager(NO_JTA_PLATFORM_CLASSES); - } - - private Object getJtaPlatformManager(String[] candidates) { - for (String candidate : candidates) { - try { - return Class.forName(candidate).newInstance(); - } - catch (Exception ex) { - // Continue searching - } - } - throw new IllegalStateException("Could not configure JTA platform"); - } +@Import(JpaHibernateConfiguration.class) +public class HibernateJpaAutoConfiguration { @Order(Ordered.HIGHEST_PRECEDENCE + 20) static class HibernateEntityManagerCondition extends SpringBootCondition { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaHibernateConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaHibernateConfiguration.java new file mode 100644 index 0000000000..15e6543b6f --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaHibernateConfiguration.java @@ -0,0 +1,190 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.orm.jpa; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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.orm.jpa.hibernate.SpringJtaPlatform; +import org.springframework.context.annotation.Configuration; +import org.springframework.jndi.JndiLocatorDelegate; +import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.jta.JtaTransactionManager; +import org.springframework.util.ClassUtils; + +/** + * {@link JpaBaseConfiguration} implementation for Hibernate. + * + * @author Phillip Webb + * @author Josh Long + * @author Manuel Doninger + * @author Andy Wilkinson + * @author Stephane Nicoll + * @since 2.0.0 + */ +@Configuration +@ConditionalOnSingleCandidate(DataSource.class) +public class JpaHibernateConfiguration extends JpaBaseConfiguration { + + private static final Log logger = LogFactory + .getLog(JpaHibernateConfiguration.class); + + private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform"; + + /** + * {@code NoJtaPlatform} implementations for various Hibernate versions. + */ + private static final String[] NO_JTA_PLATFORM_CLASSES = { + "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform", + "org.hibernate.service.jta.platform.internal.NoJtaPlatform" }; + + /** + * {@code WebSphereExtendedJtaPlatform} implementations for various Hibernate + * versions. + */ + private static final String[] WEBSPHERE_JTA_PLATFORM_CLASSES = { + "org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform", + "org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform", }; + + private final HibernateDefaultDdlAutoProvider defaultDdlAutoProvider; + + public JpaHibernateConfiguration(DataSource dataSource, + JpaProperties jpaProperties, + ObjectProvider jtaTransactionManager, + ObjectProvider transactionManagerCustomizers, + ObjectProvider> providers) { + super(dataSource, jpaProperties, jtaTransactionManager, + transactionManagerCustomizers); + this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider( + providers.getIfAvailable(Collections::emptyList)); + } + + @Override + protected AbstractJpaVendorAdapter createJpaVendorAdapter() { + return new HibernateJpaVendorAdapter(); + } + + @Override + protected Map getVendorProperties() { + Map vendorProperties = new LinkedHashMap<>(); + String defaultDdlMode = this.defaultDdlAutoProvider + .getDefaultDdlAuto(getDataSource()); + vendorProperties.putAll(getProperties().getHibernateProperties(defaultDdlMode)); + return vendorProperties; + } + + @Override + protected void customizeVendorProperties(Map vendorProperties) { + super.customizeVendorProperties(vendorProperties); + if (!vendorProperties.containsKey(JTA_PLATFORM)) { + configureJtaPlatform(vendorProperties); + } + } + + private void configureJtaPlatform(Map vendorProperties) + throws LinkageError { + JtaTransactionManager jtaTransactionManager = getJtaTransactionManager(); + if (jtaTransactionManager != null) { + if (runningOnWebSphere()) { + // We can never use SpringJtaPlatform on WebSphere as + // WebSphereUowTransactionManager has a null TransactionManager + // which will cause Hibernate to NPE + configureWebSphereTransactionPlatform(vendorProperties); + } + else { + configureSpringJtaPlatform(vendorProperties, jtaTransactionManager); + } + } + else { + vendorProperties.put(JTA_PLATFORM, getNoJtaPlatformManager()); + } + } + + private boolean runningOnWebSphere() { + return ClassUtils.isPresent( + "com.ibm.websphere.jtaextensions." + "ExtendedJTATransaction", + getClass().getClassLoader()); + } + + private void configureWebSphereTransactionPlatform( + Map vendorProperties) { + vendorProperties.put(JTA_PLATFORM, getWebSphereJtaPlatformManager()); + } + + private Object getWebSphereJtaPlatformManager() { + return getJtaPlatformManager(WEBSPHERE_JTA_PLATFORM_CLASSES); + } + + private void configureSpringJtaPlatform(Map vendorProperties, + JtaTransactionManager jtaTransactionManager) { + try { + vendorProperties.put(JTA_PLATFORM, + new SpringJtaPlatform(jtaTransactionManager)); + } + catch (LinkageError ex) { + // NoClassDefFoundError can happen if Hibernate 4.2 is used and some + // containers (e.g. JBoss EAP 6) wraps it in the superclass LinkageError + if (!isUsingJndi()) { + throw new IllegalStateException("Unable to set Hibernate JTA " + + "platform, are you using the correct " + + "version of Hibernate?", ex); + } + // Assume that Hibernate will use JNDI + if (logger.isDebugEnabled()) { + logger.debug("Unable to set Hibernate JTA platform : " + ex.getMessage()); + } + } + } + + private boolean isUsingJndi() { + try { + return JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable(); + } + catch (Error ex) { + return false; + } + } + + private Object getNoJtaPlatformManager() { + return getJtaPlatformManager(NO_JTA_PLATFORM_CLASSES); + } + + private Object getJtaPlatformManager(String[] candidates) { + for (String candidate : candidates) { + try { + return Class.forName(candidate).newInstance(); + } + catch (Exception ex) { + // Continue searching + } + } + throw new IllegalStateException("Could not configure JTA platform"); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java index 998403975e..d265f94188 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; @@ -26,17 +27,20 @@ import javax.sql.DataSource; import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform; import org.junit.Test; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -75,28 +79,49 @@ public abstract class AbstractJpaAutoConfigurationTests { } @Test - public void dataSourceIsNotAvailable() { + public void notConfiguredIfDataSourceIsNotAvailable() { new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(this.autoConfiguredClass)) - .run((context) -> { - assertThat(context).hasFailed(); - assertThat(context.getStartupFailure()) - .isInstanceOf(BeanCreationException.class); - assertThat(context.getStartupFailure()) - .hasMessageContaining("No qualifying bean"); - assertThat(context.getStartupFailure()) - .hasMessageContaining("DataSource"); - }); + .run(assertJpaIsNotAutoConfigured()); } @Test - public void dataSourceIsCreatedWithDefaultConfig() { + public void notConfiguredIfNoSingleDataSourceCandidateIsAvailable() { + new ApplicationContextRunner() + .withUserConfiguration(TestTwoDataSourcesConfiguration.class) + .withConfiguration(AutoConfigurations.of(this.autoConfiguredClass)) + .run(assertJpaIsNotAutoConfigured()); + } + + protected ContextConsumer assertJpaIsNotAutoConfigured() { + return (context) -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(JpaProperties.class); + assertThat(context).doesNotHaveBean(PlatformTransactionManager.class); + assertThat(context).doesNotHaveBean(EntityManagerFactory.class); + }; + } + + @Test + public void configuredWithAutoConfiguredDataSource() { this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(DataSource.class); assertThat(context).hasSingleBean(JpaTransactionManager.class); + assertThat(context).hasSingleBean(EntityManagerFactory.class); }); } + @Test + public void configuredWithSingleCandidateDataSource() { + this.contextRunner.withUserConfiguration( + TestTwoDataSourcesAndPrimaryConfiguration.class).run((context) -> { + assertThat(context).getBeans(DataSource.class).hasSize(2); + assertThat(context).hasSingleBean(JpaTransactionManager.class); + assertThat(context).hasSingleBean(EntityManagerFactory.class); + }); + + } + @Test public void jtaTransactionManagerTakesPrecedence() { this.contextRunner @@ -215,6 +240,49 @@ public abstract class AbstractJpaAutoConfigurationTests { }); } + + + @Configuration + protected static class TestTwoDataSourcesConfiguration { + + @Bean + public DataSource firstDataSource() { + return createRandomDataSource(); + } + + @Bean + public DataSource secondDataSource() { + return createRandomDataSource(); + } + + private DataSource createRandomDataSource() { + String url = "jdbc:h2:mem:init-" + UUID.randomUUID().toString(); + return DataSourceBuilder.create().url(url).build(); + } + + } + + @Configuration + static class TestTwoDataSourcesAndPrimaryConfiguration { + + @Bean + @Primary + public DataSource firstDataSource() { + return createRandomDataSource(); + } + + @Bean + public DataSource secondDataSource() { + return createRandomDataSource(); + } + + private DataSource createRandomDataSource() { + String url = "jdbc:h2:mem:init-" + UUID.randomUUID().toString(); + return DataSourceBuilder.create().url(url).build(); + } + + } + @Configuration @TestAutoConfigurationPackage(City.class) protected static class TestConfiguration { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 6b1c348c12..6125c24737 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -41,6 +41,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration; @@ -73,12 +75,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 @@ -103,7 +105,7 @@ public class HibernateJpaAutoConfigurationTests "spring.datasource.data:classpath:/city.sql") .run((context) -> assertThat( context.getBean(TestInitializedJpaConfiguration.class).called) - .isTrue()); + .isTrue()); } @Test @@ -162,7 +164,7 @@ public class HibernateJpaAutoConfigurationTests .getJpaPropertyMap(); assertThat((String) jpaPropertyMap .get("hibernate.transaction.jta.platform")) - .isEqualTo(TestJtaPlatform.class.getName()); + .isEqualTo(TestJtaPlatform.class.getName()); }); } @@ -179,6 +181,17 @@ public class HibernateJpaAutoConfigurationTests }); } + @Test + public void autoConfigurationBacksOffWithSeveralDataSources() { + contextRunner().withConfiguration( + AutoConfigurations.of(DataSourceTransactionManagerAutoConfiguration.class, + XADataSourceAutoConfiguration.class, JtaAutoConfiguration.class) + ).withUserConfiguration(TestTwoDataSourcesConfiguration.class).run((context) -> { + assertThat(context).hasNotFailed(); + assertThat(context).doesNotHaveBean(EntityManagerFactory.class); + }); + } + @Configuration @TestAutoConfigurationPackage(City.class) static class TestInitializedJpaConfiguration {