From f2e3d94fa1eefd5159b0b4494bce2e26818c0de0 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Sun, 18 May 2014 09:51:35 +0100 Subject: [PATCH] Use Hibernate native APIs to defer processing DDL The EntityManagerFactory will happily process the DDL on startup, but that happens too early (because of LoadtimeWeaverAware processing). We can defer it to a more civilised stage, e.g. ContextRefreshedEvent by using the Hibernate native APIs directly. It makes the JpaProperties slightly more complex because they need to distinguish between the early init and late processing versions of the Hibernate properties. Not ready for prime time yet because there is no way to deal with multiple EntityManagers. Fixes gh-894 --- .../flyway/FlywayAutoConfiguration.java | 23 -------- .../orm/jpa/EntityManagerFactoryBuilder.java | 3 -- .../jpa/HibernateJpaAutoConfiguration.java | 37 ++++++++++++- .../orm/jpa/JpaBaseConfiguration.java | 6 ++- .../autoconfigure/orm/jpa/JpaProperties.java | 53 +++++++++++++++---- ...tomHibernateJpaAutoConfigurationTests.java | 21 ++++---- 6 files changed, 97 insertions(+), 46 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index 03eb7957f6d..e4ad638d90f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -20,9 +20,7 @@ import javax.annotation.PostConstruct; import javax.sql.DataSource; import org.flywaydb.core.Flyway; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -33,7 +31,6 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -96,26 +93,6 @@ public class FlywayAutoConfiguration { return flyway; } - @Bean - @DependsOn("flyway") - protected BeanPostProcessor forceFlywayToInitialize() { - - return new BeanPostProcessor() { - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } - - }; - } } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java index 15fc27661b7..f603e422479 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java @@ -146,9 +146,6 @@ public class EntityManagerFactoryBuilder { entityManagerFactoryBean.setPackagesToScan(this.packagesToScan); entityManagerFactoryBean.getJpaPropertyMap().putAll( EntityManagerFactoryBuilder.this.properties.getProperties()); - entityManagerFactoryBean.getJpaPropertyMap().putAll( - EntityManagerFactoryBuilder.this.properties - .getHibernateProperties(this.dataSource)); return entityManagerFactoryBean; } 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 2d059bb912e..253d25301f4 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 @@ -16,8 +16,14 @@ package org.springframework.boot.autoconfigure.orm.jpa; +import java.util.Map; + import javax.persistence.EntityManager; +import javax.sql.DataSource; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; @@ -25,9 +31,11 @@ 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.context.ApplicationListener; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; @@ -45,13 +53,40 @@ import org.springframework.util.ClassUtils; EnableTransactionManagement.class, EntityManager.class }) @Conditional(HibernateEntityManagerCondition.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) -public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { +public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implements + ApplicationListener { + + @Autowired + private JpaProperties properties; + + @Autowired + private DataSource dataSource; + + @Autowired + private BeanFactory beanFactory; @Override protected AbstractJpaVendorAdapter createJpaVendorAdapter() { return new HibernateJpaVendorAdapter(); } + @Override + protected Map getVendorProperties() { + return this.properties.getInitialHibernateProperties(this.dataSource); + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + Map map = this.properties.getHibernateProperties(this.dataSource); + if ("none".equals(map.get("hibernate.hbm2ddl.auto"))) { + return; + } + LocalContainerEntityManagerFactoryBean factory = this.beanFactory + .getBean(LocalContainerEntityManagerFactoryBean.class); + Bootstrap.getEntityManagerFactoryBuilder(factory.getPersistenceUnitInfo(), map) + .generateSchema(); + } + static class HibernateEntityManagerCondition extends SpringBootCondition { private static String[] CLASS_NAMES = { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java index 4d30acaf372..8b07c87e05f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; import java.util.List; +import java.util.Map; import javax.sql.DataSource; @@ -97,11 +98,14 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { @ConditionalOnMissingBean public LocalContainerEntityManagerFactoryBean entityManagerFactory( EntityManagerFactoryBuilder factory) { - return factory.dataSource(this.dataSource).packages(getPackagesToScan()).build(); + return factory.dataSource(this.dataSource).packages(getPackagesToScan()) + .properties(getVendorProperties()).build(); } protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter(); + protected abstract Map getVendorProperties(); + protected String[] getPackagesToScan() { List basePackages = AutoConfigurationPackages.get(this.beanFactory); return basePackages.toArray(new String[basePackages.size()]); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java index 3934e44a414..cf61e897db0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java @@ -95,8 +95,27 @@ public class JpaProperties { this.hibernate = hibernate; } + /** + * Get configuration properties for the initialization of the main + * EntityManagerFactory. The result will always have ddl-auto=none, so that the schema + * generation or validation can be deferred to a later stage. + * + * @param dataSource the DataSource in case it is needed to determine the properties + * @return some Hibernate properties for configuration + */ + public Map getInitialHibernateProperties(DataSource dataSource) { + return this.hibernate.getAdditionalProperties(this.properties); + } + + /** + * Get the full configuration properties the Hibernate EntityManagerFactory. + * + * @param dataSource the DataSource in case it is needed to determine the properties + * @return some Hibernate properties for configuration + */ public Map getHibernateProperties(DataSource dataSource) { - return this.hibernate.getAdditionalProperties(this.properties, dataSource); + return this.hibernate + .getDeferredAdditionalProperties(this.properties, dataSource); } public static class Hibernate { @@ -116,15 +135,35 @@ public class JpaProperties { } public String getDdlAuto() { - return this.ddlAuto; + return "none"; + } + + private String getDeferredDdlAuto(Map existing, + DataSource dataSource) { + String ddlAuto = this.ddlAuto != null ? this.ddlAuto + : getDefaultDdlAuto(dataSource); + if (!isAlreadyProvided(existing, "hbm2ddl.auto") && !"none".equals(ddlAuto)) { + return ddlAuto; + } + if (isAlreadyProvided(existing, "hbm2ddl.auto")) { + return (String) existing.get("hibernate.hbm2ddl.auto"); + } + return "none"; } public void setDdlAuto(String ddlAuto) { this.ddlAuto = ddlAuto; } - private Map getAdditionalProperties(Map existing, - DataSource dataSource) { + private Map getDeferredAdditionalProperties( + Map properties, DataSource dataSource) { + Map deferred = getAdditionalProperties(properties); + deferred.put("hibernate.hbm2ddl.auto", + getDeferredDdlAuto(properties, dataSource)); + return deferred; + } + + private Map getAdditionalProperties(Map existing) { Map result = new HashMap(); if (!isAlreadyProvided(existing, "ejb.naming_strategy") && this.namingStrategy != null) { @@ -134,11 +173,7 @@ public class JpaProperties { result.put("hibernate.ejb.naming_strategy", DEFAULT_NAMING_STRATEGY.getName()); } - String ddlAuto = this.ddlAuto != null ? this.ddlAuto - : getDefaultDdlAuto(dataSource); - if (!isAlreadyProvided(existing, "hbm2ddl.auto") && !"none".equals(ddlAuto)) { - result.put("hibernate.hbm2ddl.auto", ddlAuto); - } + result.put("hibernate.hbm2ddl.auto", "none"); return result; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java index dd232df8efb..4b4908bba31 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.orm.jpa; +import javax.sql.DataSource; + import org.junit.After; import org.junit.Test; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; @@ -26,7 +28,6 @@ import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; @@ -58,11 +59,12 @@ public class CustomHibernateJpaAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class, HibernateJpaAutoConfiguration.class); this.context.refresh(); - LocalContainerEntityManagerFactoryBean bean = this.context - .getBean(LocalContainerEntityManagerFactoryBean.class); - String actual = (String) bean.getJpaPropertyMap().get("hibernate.hbm2ddl.auto"); - // No default (let Hibernate choose) - assertThat(actual, equalTo(null)); + JpaProperties bean = this.context.getBean(JpaProperties.class); + DataSource dataSource = this.context.getBean(DataSource.class); + String actual = (String) bean.getHibernateProperties(dataSource).get( + "hibernate.hbm2ddl.auto"); + // Default is generic and safe + assertThat(actual, equalTo("none")); } @Test @@ -74,9 +76,10 @@ public class CustomHibernateJpaAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class, HibernateJpaAutoConfiguration.class); this.context.refresh(); - LocalContainerEntityManagerFactoryBean bean = this.context - .getBean(LocalContainerEntityManagerFactoryBean.class); - String actual = (String) bean.getJpaPropertyMap().get("hibernate.hbm2ddl.auto"); + JpaProperties bean = this.context.getBean(JpaProperties.class); + DataSource dataSource = this.context.getBean(DataSource.class); + String actual = (String) bean.getHibernateProperties(dataSource).get( + "hibernate.hbm2ddl.auto"); assertThat(actual, equalTo("create-drop")); }