From 74166e770a494219922cd150bbab10239aa65c51 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 4 Jun 2014 09:44:02 +0100 Subject: [PATCH] Revert deferred DDL changes and re-oreder database migrations Schema initialization now happens in @PostConstruct (effectively) whether it is via the Hibernate EntityManagerFactory or the Boot DataSourceInitialization (in addition or instead). The data.sql script if it exists is still executed on an event fired from the other places, so those tests are passing. Flyway and liquibase have bean factory post processors (like the one they use to order the audit aspect in Spring Data) that enforce a dependency on those components from the EntityManagerFactory. So Hibernate validation is still happy (and there are 2 tests to prove it now as well). Fixes gh-1022 --- .../flyway/FlywayAutoConfiguration.java | 72 ++++++++++++++++++ .../jdbc/DataSourceInitialization.java | 45 ++++------- .../jdbc/DataSourceProperties.java | 10 --- .../liquibase/LiquibaseAutoConfiguration.java | 74 +++++++++++++++++++ .../jpa/DataSourceInitializedPublisher.java | 53 +++++++++++++ .../jpa/HibernateJpaAutoConfiguration.java | 48 +----------- .../orm/jpa/JpaBaseConfiguration.java | 32 +++++++- .../autoconfigure/orm/jpa/JpaProperties.java | 55 +++----------- .../HibernateJpaAutoConfigurationTests.java | 32 +++++++- .../db/changelog/db.changelog-city.yaml | 36 +++++++++ .../src/test/resources/db/city/V1__init.sql | 7 ++ .../appendix-application-properties.adoc | 2 - 12 files changed, 329 insertions(+), 137 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java create mode 100644 spring-boot-autoconfigure/src/test/resources/db/changelog/db.changelog-city.yaml create mode 100644 spring-boot-autoconfigure/src/test/resources/db/city/V1__init.sql 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 24cbb482727..c3fa2442d4f 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 @@ -16,11 +16,21 @@ package org.springframework.boot.autoconfigure.flyway; +import java.util.HashSet; +import java.util.Set; + import javax.annotation.PostConstruct; +import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.flywaydb.core.Flyway; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -32,10 +42,18 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import static java.util.Arrays.asList; +import static org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors; +import static org.springframework.beans.factory.BeanFactoryUtils.transformedBeanName; /** * {@link EnableAutoConfiguration Auto-configuration} for Flyway database migrations. @@ -53,6 +71,7 @@ public class FlywayAutoConfiguration { @Configuration @ConditionalOnMissingBean(Flyway.class) @EnableConfigurationProperties(FlywayProperties.class) + @Import(FlywayJpaDependencyConfiguration.class) public static class FlywayConfiguration { @Autowired @@ -105,4 +124,57 @@ public class FlywayAutoConfiguration { } + @Configuration + @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) + @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) + protected static class FlywayJpaDependencyConfiguration implements + BeanFactoryPostProcessor { + + public static final String FLYWAY_JPA_BEAN_NAME = "flyway"; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + + for (String beanName : getEntityManagerFactoryBeanNames(beanFactory)) { + BeanDefinition definition = getBeanDefinition(beanName, beanFactory); + definition.setDependsOn(StringUtils.addStringToArray( + definition.getDependsOn(), FLYWAY_JPA_BEAN_NAME)); + } + } + + private static BeanDefinition getBeanDefinition(String beanName, + ConfigurableListableBeanFactory beanFactory) { + try { + return beanFactory.getBeanDefinition(beanName); + } + catch (NoSuchBeanDefinitionException e) { + + BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); + + if (parentBeanFactory instanceof ConfigurableListableBeanFactory) { + return getBeanDefinition(beanName, + (ConfigurableListableBeanFactory) parentBeanFactory); + } + + throw e; + } + } + + private static Iterable getEntityManagerFactoryBeanNames( + ListableBeanFactory beanFactory) { + + Set names = new HashSet(); + names.addAll(asList(beanNamesForTypeIncludingAncestors(beanFactory, + EntityManagerFactory.class, true, false))); + + for (String factoryBeanName : beanNamesForTypeIncludingAncestors(beanFactory, + AbstractEntityManagerFactoryBean.class, true, false)) { + names.add(transformedBeanName(factoryBeanName)); + } + + return names; + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitialization.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitialization.java index d61eb5242b6..4adac0ee2aa 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitialization.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitialization.java @@ -33,7 +33,6 @@ import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.io.Resource; import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; @@ -44,12 +43,11 @@ import org.springframework.util.StringUtils; */ @Configuration @EnableConfigurationProperties(DataSourceProperties.class) -public class DataSourceInitialization implements - ApplicationListener { +public class DataSourceInitialization { private static Log logger = LogFactory.getLog(DataSourceAutoConfiguration.class); - @Autowired(required = false) + @Autowired private DataSource dataSource; @Autowired @@ -61,34 +59,11 @@ public class DataSourceInitialization implements private boolean initialized = false; @Bean - public ApplicationListener dataSourceInitializedListener() { + public ApplicationListener dataSourceInitializedListener( + DataSource dataSource) { return new DataSourceInitializedListener(); } - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - if (this.properties.isDeferDdl()) { - boolean initialize = this.properties.isInitialize(); - if (!initialize) { - logger.debug("Initialization disabled (not running DDL scripts)"); - return; - } - runSchemaScripts(); - } - } - - @PostConstruct - protected void initialize() { - if (!this.properties.isDeferDdl()) { - boolean initialize = this.properties.isInitialize(); - if (!initialize) { - logger.debug("Initialization disabled (not running DDL scripts)"); - return; - } - runSchemaScripts(); - } - } - private void runSchemaScripts() { String schema = this.properties.getSchema(); if (schema == null) { @@ -172,6 +147,18 @@ public class DataSourceInitialization implements private class DataSourceInitializedListener implements ApplicationListener { + // Keep this in the nested class so that it doesn't have to be called before the + // listener is instantiated (ordering problems otherwise) + @PostConstruct + protected void initialize() { + boolean initialize = DataSourceInitialization.this.properties.isInitialize(); + if (!initialize) { + logger.debug("Initialization disabled (not running DDL scripts)"); + return; + } + runSchemaScripts(); + } + @Override public void onApplicationEvent(DataSourceInitializedEvent event) { runDataScripts(); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 2f81c584548..1e888798b8f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -46,8 +46,6 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB private boolean initialize = true; - private boolean deferDdl = false; - private String platform = "all"; private String schema; @@ -158,14 +156,6 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB this.initialize = initialize; } - public void setDeferDdl(boolean deferDdl) { - this.deferDdl = deferDdl; - } - - public boolean isDeferDdl() { - return this.deferDdl; - } - public String getPlatform() { return this.platform; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java index 05f14c10ffc..596916662d6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java @@ -16,12 +16,22 @@ package org.springframework.boot.autoconfigure.liquibase; +import java.util.HashSet; +import java.util.Set; + import javax.annotation.PostConstruct; +import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import liquibase.integration.spring.SpringLiquibase; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -32,15 +42,24 @@ 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.Import; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import static java.util.Arrays.asList; +import static org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors; +import static org.springframework.beans.factory.BeanFactoryUtils.transformedBeanName; /** * {@link EnableAutoConfiguration Auto-configuration} for Liquibase. * * @author Marcel Overdijk + * @author Dave Syer * @since 1.1.0 */ @Configuration @@ -53,6 +72,7 @@ public class LiquibaseAutoConfiguration { @Configuration @ConditionalOnMissingBean(SpringLiquibase.class) @EnableConfigurationProperties(LiquibaseProperties.class) + @Import(LiquibaseJpaDependencyConfiguration.class) public static class LiquibaseConfiguration { @Autowired @@ -87,4 +107,58 @@ public class LiquibaseAutoConfiguration { return liquibase; } } + + @Configuration + @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) + @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) + protected static class LiquibaseJpaDependencyConfiguration implements + BeanFactoryPostProcessor { + + public static final String LIQUIBASE_JPA_BEAN_NAME = "liquibase"; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + + for (String beanName : getEntityManagerFactoryBeanNames(beanFactory)) { + BeanDefinition definition = getBeanDefinition(beanName, beanFactory); + definition.setDependsOn(StringUtils.addStringToArray( + definition.getDependsOn(), LIQUIBASE_JPA_BEAN_NAME)); + } + } + + private static BeanDefinition getBeanDefinition(String beanName, + ConfigurableListableBeanFactory beanFactory) { + try { + return beanFactory.getBeanDefinition(beanName); + } + catch (NoSuchBeanDefinitionException e) { + + BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); + + if (parentBeanFactory instanceof ConfigurableListableBeanFactory) { + return getBeanDefinition(beanName, + (ConfigurableListableBeanFactory) parentBeanFactory); + } + + throw e; + } + } + + private static Iterable getEntityManagerFactoryBeanNames( + ListableBeanFactory beanFactory) { + + Set names = new HashSet(); + names.addAll(asList(beanNamesForTypeIncludingAncestors(beanFactory, + EntityManagerFactory.class, true, false))); + + for (String factoryBeanName : beanNamesForTypeIncludingAncestors(beanFactory, + AbstractEntityManagerFactoryBean.class, true, false)) { + names.add(transformedBeanName(factoryBeanName)); + } + + return names; + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java new file mode 100644 index 00000000000..751421c382e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2013 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 javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.jdbc.DataSourceInitialization.DataSourceInitializedEvent; +import org.springframework.context.ApplicationContext; + +public class DataSourceInitializedPublisher implements BeanPostProcessor { + + @Autowired + private ApplicationContext applicationContext; + private DataSource dataSource; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof DataSource) { + // Normally this will be the right DataSource + this.dataSource = (DataSource) bean; + } + if (bean instanceof EntityManagerFactory && this.dataSource != null) { + this.applicationContext.publishEvent(new DataSourceInitializedEvent( + this.dataSource)); + } + return bean; + } +} \ No newline at end of file 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 a9e998f685a..3948858c8f8 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 @@ -21,7 +21,6 @@ import java.util.Map; import javax.persistence.EntityManager; import javax.sql.DataSource; -import org.hibernate.jpa.boot.spi.Bootstrap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -29,15 +28,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 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.jdbc.DataSourceInitialization.DataSourceInitializedEvent; -import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.HibernateEntityManagerCondition; -import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; 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; @@ -73,48 +68,7 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { @Override protected Map getVendorProperties() { - return this.properties.getInitialHibernateProperties(this.dataSource); - } - - @Override - protected EntityManagerFactoryBeanCallback getVendorCallback() { - final Map map = this.properties - .getHibernateProperties(this.dataSource); - return new EntityManagerFactoryBeanCallback() { - @Override - public void execute( - LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { - HibernateJpaAutoConfiguration.this.applicationContext - .addApplicationListener(new DeferredSchemaAction( - entityManagerFactoryBean, map)); - } - }; - } - - private class DeferredSchemaAction implements - ApplicationListener { - - private Map map; - private LocalContainerEntityManagerFactoryBean factory; - - public DeferredSchemaAction(LocalContainerEntityManagerFactoryBean factory, - Map map) { - this.factory = factory; - this.map = map; - } - - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - String ddlAuto = this.map.get("hibernate.hbm2ddl.auto"); - if (ddlAuto == null || "none".equals(ddlAuto) || "".equals(ddlAuto)) { - return; - } - Bootstrap.getEntityManagerFactoryBuilder( - this.factory.getPersistenceUnitInfo(), this.map).generateSchema(); - HibernateJpaAutoConfiguration.this.applicationContext - .publishEvent(new DataSourceInitializedEvent( - HibernateJpaAutoConfiguration.this.dataSource)); - } + return this.properties.getHibernateProperties(this.dataSource); } static class HibernateEntityManagerCondition extends SpringBootCondition { 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 e6acf57fcd5..370dce5e2a5 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 @@ -25,16 +25,23 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.DataSourceInitializedRegistrar; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.Primary; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -54,6 +61,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter * @author Oliver Gierke */ @EnableConfigurationProperties(JpaProperties.class) +@Import(DataSourceInitializedRegistrar.class) public abstract class JpaBaseConfiguration implements BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @@ -107,7 +115,9 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { protected abstract Map getVendorProperties(); - protected abstract EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback(); + protected EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback() { + return null; + } protected String[] getPackagesToScan() { if (AutoConfigurationPackages.has(this.beanFactory)) { @@ -145,4 +155,24 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { } + protected static class DataSourceInitializedRegistrar implements + ImportBeanDefinitionRegistrar { + + private static final String BEAN_NAME = "dataSourceInitializedPublisher"; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition(BEAN_NAME)) { + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(DataSourceInitializedPublisher.class); + beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + // We don't need this one to be post processed otherwise it can cause a + // cascade of bean instantiation that we would rather avoid. + beanDefinition.setSynthetic(true); + registry.registerBeanDefinition(BEAN_NAME, beanDefinition); + } + } + } + } 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 085923b648d..463802315c9 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 @@ -103,23 +103,13 @@ public class JpaProperties { /** * Get configuration properties for the initialization of the main Hibernate - * 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 for the Hibernate EntityManagerFactory. + * 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 - .getDeferredAdditionalProperties(this.properties, dataSource); + return this.hibernate.getAdditionalProperties(this.properties, dataSource); } public static class Hibernate { @@ -130,8 +120,6 @@ public class JpaProperties { private String ddlAuto; - private boolean deferDdl = true; - public Class getNamingStrategy() { return this.namingStrategy; } @@ -143,7 +131,7 @@ public class JpaProperties { @Deprecated public void setNamingstrategy(Class namingStrategy) { logger.warn("The property spring.jpa.namingstrategy has been renamed, " - + "please update your configuration to use naming-strategy"); + + "please update your configuration to use namingStrategy or naming-strategy or naming_strategy"); this.setNamingStrategy(namingStrategy); } @@ -151,19 +139,8 @@ public class JpaProperties { return this.ddlAuto; } - public void setDeferDdl(boolean deferDdl) { - this.deferDdl = deferDdl; - } - - public boolean isDeferDdl() { - return this.deferDdl; - } - - private String getDeferredDdlAuto(Map existing, + private String getActualDdlAuto(Map existing, DataSource dataSource) { - if (!this.deferDdl) { - return "none"; - } String ddlAuto = this.ddlAuto != null ? this.ddlAuto : getDefaultDdlAuto(dataSource); if (!isAlreadyProvided(existing, "hbm2ddl.auto") && !"none".equals(ddlAuto)) { @@ -179,19 +156,8 @@ public class JpaProperties { this.ddlAuto = ddlAuto; } - private Map getDeferredAdditionalProperties( - Map properties, DataSource dataSource) { - Map deferred = getAdditionalProperties(properties); - String ddlAuto = getDeferredDdlAuto(properties, dataSource); - if (StringUtils.hasText(ddlAuto) && !"none".equals(ddlAuto)) { - deferred.put("hibernate.hbm2ddl.auto", ddlAuto); - } else { - deferred.remove("hibernate.hbm2ddl.auto"); - } - return deferred; - } - - private Map getAdditionalProperties(Map existing) { + private Map getAdditionalProperties(Map existing, + DataSource dataSource) { Map result = new HashMap(); if (!isAlreadyProvided(existing, "ejb.naming_strategy") && this.namingStrategy != null) { @@ -201,11 +167,12 @@ public class JpaProperties { result.put("hibernate.ejb.naming_strategy", DEFAULT_NAMING_STRATEGY.getName()); } - if (this.deferDdl) { - result.remove("hibernate.hbm2ddl.auto"); + String ddlAuto = getActualDdlAuto(existing, dataSource); + if (StringUtils.hasText(ddlAuto) && !"none".equals(ddlAuto)) { + result.put("hibernate.hbm2ddl.auto", ddlAuto); } else { - result.put("hibernate.hbm2ddl.auto", this.ddlAuto); + result.remove("hibernate.hbm2ddl.auto"); } return result; } 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 a4561af5176..b81bdfc5bac 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 @@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.orm.jpa; import javax.sql.DataSource; import org.junit.Test; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -43,9 +45,10 @@ public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigura } @Test - public void testDataScriptWithDdlAuto() throws Exception { + public void testDataScriptWithMissingDdl() throws Exception { EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.data:classpath:/city.sql", + // Missing: "spring.datasource.schema:classpath:/ddl.sql"); setupTestConfiguration(); this.context.refresh(); @@ -55,10 +58,9 @@ public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigura } @Test - public void testDataScriptWithDeferredDdl() throws Exception { + public void testDataScript() throws Exception { EnvironmentTestUtils.addEnvironment(this.context, - "spring.datasource.data:classpath:/city.sql", - "spring.datasource.deferDdl:true"); + "spring.datasource.data:classpath:/city.sql"); setupTestConfiguration(); this.context.refresh(); assertEquals(new Integer(1), @@ -111,4 +113,26 @@ public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigura assertThat(actual, not(equalTo("org.hibernate.cfg.EJB3NamingStrategy"))); } + @Test + public void testFlywayPlusValidation() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false", + "flyway.locations:classpath:db/city", + "spring.jpa.hibernate.ddl-auto:validate"); + setupTestConfiguration(); + this.context.register(FlywayAutoConfiguration.class); + this.context.refresh(); + } + + @Test + public void testLiquibasePlusValidation() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false", + "liquibase.changeLog:classpath:db/changelog/db.changelog-city.yaml", + "spring.jpa.hibernate.ddl-auto:validate"); + setupTestConfiguration(); + this.context.register(LiquibaseAutoConfiguration.class); + this.context.refresh(); + } + } diff --git a/spring-boot-autoconfigure/src/test/resources/db/changelog/db.changelog-city.yaml b/spring-boot-autoconfigure/src/test/resources/db/changelog/db.changelog-city.yaml new file mode 100644 index 00000000000..ae263b108a6 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/db/changelog/db.changelog-city.yaml @@ -0,0 +1,36 @@ +databaseChangeLog: + - changeSet: + id: 1 + author: dsyer + changes: + - createTable: + tableName: city + columns: + - column: + name: id + type: bigint + autoIncrement: true + constraints: + primaryKey: true + nullable: false + - column: + name: name + type: varchar(50) + constraints: + nullable: false + - column: + name: state + type: varchar(50) + constraints: + nullable: false + - column: + name: country + type: varchar(50) + constraints: + nullable: false + - column: + name: map + type: varchar(50) + constraints: + nullable: true + \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/db/city/V1__init.sql b/spring-boot-autoconfigure/src/test/resources/db/city/V1__init.sql new file mode 100644 index 00000000000..3f9f9d079fd --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/db/city/V1__init.sql @@ -0,0 +1,7 @@ +CREATE TABLE CITY ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + name VARCHAR(30), + state VARCHAR(30), + country VARCHAR(30), + map VARCHAR(30) +); \ No newline at end of file diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index b36e9417d2b..5c1c815a908 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -156,7 +156,6 @@ content into your application; rather pick only the properties that you need. # DATASOURCE ({sc-spring-boot-autoconfigure}/jdbc/DataSourceAutoConfiguration.{sc-ext}[DataSourceAutoConfiguration] & {sc-spring-boot-autoconfigure}//jdbc/AbstractDataSourceConfiguration.{sc-ext}[AbstractDataSourceConfiguration]) spring.datasource.name= # name of the data source spring.datasource.initialize=true # populate using data.sql - spring.datasource.deferDdl= # flag to indicate that schema scripts will run after the application starts (default false) spring.datasource.schema= # a schema (DDL) script resource reference spring.datasource.data= # a data (DML) script resource reference spring.datasource.platform= # the platform to use in the schema resource (schema-${platform}.sql) @@ -191,7 +190,6 @@ content into your application; rather pick only the properties that you need. spring.jpa.database= spring.jpa.generate-ddl=false # ignored by Hibernate, might be useful for other vendors spring.jpa.hibernate.naming-strategy= # naming classname - spring.jpa.hibernate.defer-ddl=true # defer processing of DDL until application is running spring.jpa.hibernate.ddl-auto= # defaults to create-drop for embedded dbs # SOLR ({sc-spring-boot-autoconfigure}/solr/SolrProperties.{sc-ext}[SolrProperties}])