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 f603e42247..9b0f793732 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 @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -46,6 +47,8 @@ public class EntityManagerFactoryBuilder { private JpaProperties properties; + private EntityManagerFactoryBeanCallback callback; + /** * Create a new instance passing in the common pieces that will be shared if multiple * EntityManagerFactory instances are created. @@ -66,6 +69,15 @@ public class EntityManagerFactoryBuilder { return new Builder(dataSource); } + /** + * An optional callback for new entity manager factory beans. + * + * @author Dave Syer + */ + public void setCallback(EntityManagerFactoryBeanCallback callback) { + this.callback = callback; + } + /** * A fluent builder for a LocalContainerEntityManagerFactoryBean. */ @@ -77,6 +89,8 @@ public class EntityManagerFactoryBuilder { private String persistenceUnit; + private Map properties = new HashMap(); + private Builder(DataSource dataSource) { this.dataSource = dataSource; } @@ -126,8 +140,7 @@ public class EntityManagerFactoryBuilder { * @return the builder for fluent usage */ public Builder properties(Map properties) { - EntityManagerFactoryBuilder.this.properties.getProperties() - .putAll(properties); + this.properties.putAll(properties); return this; } @@ -146,9 +159,25 @@ public class EntityManagerFactoryBuilder { entityManagerFactoryBean.setPackagesToScan(this.packagesToScan); entityManagerFactoryBean.getJpaPropertyMap().putAll( EntityManagerFactoryBuilder.this.properties.getProperties()); + entityManagerFactoryBean.getJpaPropertyMap().putAll(this.properties); + if (EntityManagerFactoryBuilder.this.callback != null) { + EntityManagerFactoryBuilder.this.callback + .execute(entityManagerFactoryBean); + } return entityManagerFactoryBean; } } + /** + * A callback for new entity manager factory beans created by a Builder. + * + * @author Dave Syer + */ + public static interface EntityManagerFactoryBeanCallback { + + void execute(LocalContainerEntityManagerFactoryBean factory); + + } + } 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 253d25301f..8d3945af11 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 @@ -22,7 +22,6 @@ 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; @@ -30,8 +29,10 @@ 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.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; @@ -53,8 +54,7 @@ import org.springframework.util.ClassUtils; EnableTransactionManagement.class, EntityManager.class }) @Conditional(HibernateEntityManagerCondition.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) -public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implements - ApplicationListener { +public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { @Autowired private JpaProperties properties; @@ -63,7 +63,7 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implemen private DataSource dataSource; @Autowired - private BeanFactory beanFactory; + private ConfigurableApplicationContext applicationContext; @Override protected AbstractJpaVendorAdapter createJpaVendorAdapter() { @@ -76,15 +76,41 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implemen } @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - Map map = this.properties.getHibernateProperties(this.dataSource); - if ("none".equals(map.get("hibernate.hbm2ddl.auto"))) { - return; + 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 static 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 = (String) this.map.get("hibernate.hbm2ddl.auto"); + if (ddlAuto == null || "none".equals(ddlAuto)) { + return; + } + Bootstrap.getEntityManagerFactoryBuilder( + this.factory.getPersistenceUnitInfo(), this.map).generateSchema(); } - LocalContainerEntityManagerFactoryBean factory = this.beanFactory - .getBean(LocalContainerEntityManagerFactoryBean.class); - Bootstrap.getEntityManagerFactoryBuilder(factory.getPersistenceUnitInfo(), map) - .generateSchema(); } 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 8b07c87e05..5fd8e3e8fb 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 @@ -90,6 +90,7 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { JpaVendorAdapter jpaVendorAdapter) { EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder( jpaVendorAdapter, this.jpaProperties, this.persistenceUnitManager); + builder.setCallback(getVendorCallback()); return builder; } @@ -106,6 +107,8 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { protected abstract Map getVendorProperties(); + protected abstract EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback(); + 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 cf61e897db..57efa9f3ee 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 @@ -96,7 +96,7 @@ public class JpaProperties { } /** - * Get configuration properties for the initialization of the main + * 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. * @@ -108,7 +108,7 @@ public class JpaProperties { } /** - * Get the full configuration properties the Hibernate EntityManagerFactory. + * Get the full configuration properties for the Hibernate EntityManagerFactory. * * @param dataSource the DataSource in case it is needed to determine the properties * @return some Hibernate properties for configuration @@ -126,6 +126,8 @@ public class JpaProperties { private String ddlAuto; + private boolean deferDdl = true; + public Class getNamingStrategy() { return this.namingStrategy; } @@ -135,11 +137,22 @@ public class JpaProperties { } public String getDdlAuto() { - return "none"; + return this.ddlAuto; + } + + public void setDeferDdl(boolean deferDdl) { + this.deferDdl = deferDdl; + } + + public boolean isDeferDdl() { + return this.deferDdl; } private String getDeferredDdlAuto(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)) { @@ -173,7 +186,12 @@ public class JpaProperties { result.put("hibernate.ejb.naming_strategy", DEFAULT_NAMING_STRATEGY.getName()); } - result.put("hibernate.hbm2ddl.auto", "none"); + if (this.deferDdl) { + result.put("hibernate.hbm2ddl.auto", "none"); + } + else { + result.put("hibernate.hbm2ddl.auto", this.ddlAuto); + } return result; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilderTests.java new file mode 100644 index 0000000000..1623978427 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilderTests.java @@ -0,0 +1,66 @@ +/* + * 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 java.util.Collections; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Dave Syer + */ +public class EntityManagerFactoryBuilderTests { + + private JpaProperties properties = new JpaProperties(); + + private DataSource dataSource1 = Mockito.mock(DataSource.class); + + private DataSource dataSource2 = Mockito.mock(DataSource.class); + + @Test + public void entityManagerFactoryPropertiesNotOverwritingDefaults() { + EntityManagerFactoryBuilder factory = new EntityManagerFactoryBuilder( + new HibernateJpaVendorAdapter(), this.properties, null); + LocalContainerEntityManagerFactoryBean result1 = factory + .dataSource(this.dataSource1) + .properties(Collections.singletonMap("foo", (Object) "spam")).build(); + assertFalse(result1.getJpaPropertyMap().isEmpty()); + assertTrue(this.properties.getProperties().isEmpty()); + } + + @Test + public void multipleEntityManagerFactoriesDoNotOverwriteEachOther() { + EntityManagerFactoryBuilder factory = new EntityManagerFactoryBuilder( + new HibernateJpaVendorAdapter(), this.properties, null); + LocalContainerEntityManagerFactoryBean result1 = factory + .dataSource(this.dataSource1) + .properties(Collections.singletonMap("foo", (Object) "spam")).build(); + assertFalse(result1.getJpaPropertyMap().isEmpty()); + LocalContainerEntityManagerFactoryBean result2 = factory.dataSource( + this.dataSource2).build(); + assertTrue(result2.getJpaPropertyMap().isEmpty()); + } + +} 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 d6d6d428b5..dc753604b9 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -167,8 +167,9 @@ content into your application; rather pick only the properties that you need. spring.jpa.show-sql=true spring.jpa.database-platform= spring.jpa.database= - spring.jpa.generate-ddl= + 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 # FLYWAY ({sc-spring-boot-autoconfigure}/flyway/FlywayProperties.{sc-ext}[FlywayProperties]) diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 66037d0304..a10fd3b6de 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1397,8 +1397,10 @@ following to your `application.properties`. NOTE: Hibernate's own internal property name for this (if you happen to remember it better) is `hibernate.hbm2ddl.auto`. You can set it, along with other Hibernate native properties, using `spring.jpa.properties.*` (the prefix is stripped before adding them -to the entity manager). Also, `spring.jpa.generate-ddl=false` switches off all -DDL generation. +to the entity manager). By default the DDL execution (or validation) is deferred until +the `ApplicationContext` has started. There is also a `spring.jpa.generate-ddl` flag, but +it is not used if Hibernate autoconfig is active because the `ddl-auto` +settings are more fine grained. diff --git a/spring-boot-samples/spring-boot-sample-flyway/src/main/java/sample/flyway/SampleFlywayApplication.java b/spring-boot-samples/spring-boot-sample-flyway/src/main/java/sample/flyway/SampleFlywayApplication.java index 0f89083ecc..71bb8c9e74 100644 --- a/spring-boot-samples/spring-boot-sample-flyway/src/main/java/sample/flyway/SampleFlywayApplication.java +++ b/spring-boot-samples/spring-boot-sample-flyway/src/main/java/sample/flyway/SampleFlywayApplication.java @@ -78,7 +78,7 @@ class Person { @Override public String toString() { - return "Person [firstName=" + firstName + ", lastname=" + lastName + return "Person [firstName=" + firstName + ", lastName=" + lastName + "]"; } }