Commit a5543f18 authored by Dave Syer's avatar Dave Syer

Add callback for modifying or inspecting LocalContainerEntityManagerFactoryBean

A callback is added in autoconfig, so that if users inject the EntityManagerFactoryBuilder
into their app and use it to create multiple EntityManagerFactories, they all get the
same deferred DDL behaviour. The deferred DDL can also be disabled by setting
spring.jpa.hibernate.deferDdl=true.

Fixes gh-894
parent f2e3d94f
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.orm.jpa; package org.springframework.boot.autoconfigure.orm.jpa;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -46,6 +47,8 @@ public class EntityManagerFactoryBuilder { ...@@ -46,6 +47,8 @@ public class EntityManagerFactoryBuilder {
private JpaProperties properties; private JpaProperties properties;
private EntityManagerFactoryBeanCallback callback;
/** /**
* Create a new instance passing in the common pieces that will be shared if multiple * Create a new instance passing in the common pieces that will be shared if multiple
* EntityManagerFactory instances are created. * EntityManagerFactory instances are created.
...@@ -66,6 +69,15 @@ public class EntityManagerFactoryBuilder { ...@@ -66,6 +69,15 @@ public class EntityManagerFactoryBuilder {
return new Builder(dataSource); 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. * A fluent builder for a LocalContainerEntityManagerFactoryBean.
*/ */
...@@ -77,6 +89,8 @@ public class EntityManagerFactoryBuilder { ...@@ -77,6 +89,8 @@ public class EntityManagerFactoryBuilder {
private String persistenceUnit; private String persistenceUnit;
private Map<String, Object> properties = new HashMap<String, Object>();
private Builder(DataSource dataSource) { private Builder(DataSource dataSource) {
this.dataSource = dataSource; this.dataSource = dataSource;
} }
...@@ -126,8 +140,7 @@ public class EntityManagerFactoryBuilder { ...@@ -126,8 +140,7 @@ public class EntityManagerFactoryBuilder {
* @return the builder for fluent usage * @return the builder for fluent usage
*/ */
public Builder properties(Map<String, Object> properties) { public Builder properties(Map<String, Object> properties) {
EntityManagerFactoryBuilder.this.properties.getProperties() this.properties.putAll(properties);
.putAll(properties);
return this; return this;
} }
...@@ -146,9 +159,25 @@ public class EntityManagerFactoryBuilder { ...@@ -146,9 +159,25 @@ public class EntityManagerFactoryBuilder {
entityManagerFactoryBean.setPackagesToScan(this.packagesToScan); entityManagerFactoryBean.setPackagesToScan(this.packagesToScan);
entityManagerFactoryBean.getJpaPropertyMap().putAll( entityManagerFactoryBean.getJpaPropertyMap().putAll(
EntityManagerFactoryBuilder.this.properties.getProperties()); EntityManagerFactoryBuilder.this.properties.getProperties());
entityManagerFactoryBean.getJpaPropertyMap().putAll(this.properties);
if (EntityManagerFactoryBuilder.this.callback != null) {
EntityManagerFactoryBuilder.this.callback
.execute(entityManagerFactoryBean);
}
return 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);
}
} }
...@@ -22,7 +22,6 @@ import javax.persistence.EntityManager; ...@@ -22,7 +22,6 @@ import javax.persistence.EntityManager;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.hibernate.jpa.boot.spi.Bootstrap; import org.hibernate.jpa.boot.spi.Bootstrap;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
...@@ -30,8 +29,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; ...@@ -30,8 +29,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 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.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.HibernateEntityManagerCondition;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -53,8 +54,7 @@ import org.springframework.util.ClassUtils; ...@@ -53,8 +54,7 @@ import org.springframework.util.ClassUtils;
EnableTransactionManagement.class, EntityManager.class }) EnableTransactionManagement.class, EntityManager.class })
@Conditional(HibernateEntityManagerCondition.class) @Conditional(HibernateEntityManagerCondition.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implements public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
ApplicationListener<ContextRefreshedEvent> {
@Autowired @Autowired
private JpaProperties properties; private JpaProperties properties;
...@@ -63,7 +63,7 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implemen ...@@ -63,7 +63,7 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implemen
private DataSource dataSource; private DataSource dataSource;
@Autowired @Autowired
private BeanFactory beanFactory; private ConfigurableApplicationContext applicationContext;
@Override @Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() { protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
...@@ -76,15 +76,41 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implemen ...@@ -76,15 +76,41 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implemen
} }
@Override @Override
public void onApplicationEvent(ContextRefreshedEvent event) { protected EntityManagerFactoryBeanCallback getVendorCallback() {
Map<String, Object> map = this.properties.getHibernateProperties(this.dataSource); final Map<String, Object> map = this.properties
if ("none".equals(map.get("hibernate.hbm2ddl.auto"))) { .getHibernateProperties(this.dataSource);
return; return new EntityManagerFactoryBeanCallback() {
@Override
public void execute(
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
HibernateJpaAutoConfiguration.this.applicationContext
.addApplicationListener(new DeferredSchemaAction(
entityManagerFactoryBean, map));
}
};
}
private static class DeferredSchemaAction implements
ApplicationListener<ContextRefreshedEvent> {
private Map<String, Object> map;
private LocalContainerEntityManagerFactoryBean factory;
public DeferredSchemaAction(LocalContainerEntityManagerFactoryBean factory,
Map<String, Object> 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 { static class HibernateEntityManagerCondition extends SpringBootCondition {
......
...@@ -90,6 +90,7 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { ...@@ -90,6 +90,7 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
JpaVendorAdapter jpaVendorAdapter) { JpaVendorAdapter jpaVendorAdapter) {
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder( EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(
jpaVendorAdapter, this.jpaProperties, this.persistenceUnitManager); jpaVendorAdapter, this.jpaProperties, this.persistenceUnitManager);
builder.setCallback(getVendorCallback());
return builder; return builder;
} }
...@@ -106,6 +107,8 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { ...@@ -106,6 +107,8 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
protected abstract Map<String, Object> getVendorProperties(); protected abstract Map<String, Object> getVendorProperties();
protected abstract EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback();
protected String[] getPackagesToScan() { protected String[] getPackagesToScan() {
List<String> basePackages = AutoConfigurationPackages.get(this.beanFactory); List<String> basePackages = AutoConfigurationPackages.get(this.beanFactory);
return basePackages.toArray(new String[basePackages.size()]); return basePackages.toArray(new String[basePackages.size()]);
......
...@@ -96,7 +96,7 @@ public class JpaProperties { ...@@ -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 * EntityManagerFactory. The result will always have ddl-auto=none, so that the schema
* generation or validation can be deferred to a later stage. * generation or validation can be deferred to a later stage.
* *
...@@ -108,7 +108,7 @@ public class JpaProperties { ...@@ -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 * @param dataSource the DataSource in case it is needed to determine the properties
* @return some Hibernate properties for configuration * @return some Hibernate properties for configuration
...@@ -126,6 +126,8 @@ public class JpaProperties { ...@@ -126,6 +126,8 @@ public class JpaProperties {
private String ddlAuto; private String ddlAuto;
private boolean deferDdl = true;
public Class<?> getNamingStrategy() { public Class<?> getNamingStrategy() {
return this.namingStrategy; return this.namingStrategy;
} }
...@@ -135,11 +137,22 @@ public class JpaProperties { ...@@ -135,11 +137,22 @@ public class JpaProperties {
} }
public String getDdlAuto() { 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<String, Object> existing, private String getDeferredDdlAuto(Map<String, Object> existing,
DataSource dataSource) { DataSource dataSource) {
if (!this.deferDdl) {
return "none";
}
String ddlAuto = this.ddlAuto != null ? this.ddlAuto String ddlAuto = this.ddlAuto != null ? this.ddlAuto
: getDefaultDdlAuto(dataSource); : getDefaultDdlAuto(dataSource);
if (!isAlreadyProvided(existing, "hbm2ddl.auto") && !"none".equals(ddlAuto)) { if (!isAlreadyProvided(existing, "hbm2ddl.auto") && !"none".equals(ddlAuto)) {
...@@ -173,7 +186,12 @@ public class JpaProperties { ...@@ -173,7 +186,12 @@ public class JpaProperties {
result.put("hibernate.ejb.naming_strategy", result.put("hibernate.ejb.naming_strategy",
DEFAULT_NAMING_STRATEGY.getName()); 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; return result;
} }
......
/*
* 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());
}
}
...@@ -167,8 +167,9 @@ content into your application; rather pick only the properties that you need. ...@@ -167,8 +167,9 @@ content into your application; rather pick only the properties that you need.
spring.jpa.show-sql=true spring.jpa.show-sql=true
spring.jpa.database-platform= spring.jpa.database-platform=
spring.jpa.database= 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.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 spring.jpa.hibernate.ddl-auto= # defaults to create-drop for embedded dbs
# FLYWAY ({sc-spring-boot-autoconfigure}/flyway/FlywayProperties.{sc-ext}[FlywayProperties]) # FLYWAY ({sc-spring-boot-autoconfigure}/flyway/FlywayProperties.{sc-ext}[FlywayProperties])
......
...@@ -1397,8 +1397,10 @@ following to your `application.properties`. ...@@ -1397,8 +1397,10 @@ following to your `application.properties`.
NOTE: Hibernate's own internal property name for this (if you happen to remember it 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 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 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 to the entity manager). By default the DDL execution (or validation) is deferred until
DDL generation. 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.
......
...@@ -78,7 +78,7 @@ class Person { ...@@ -78,7 +78,7 @@ class Person {
@Override @Override
public String toString() { public String toString() {
return "Person [firstName=" + firstName + ", lastname=" + lastName return "Person [firstName=" + firstName + ", lastName=" + lastName
+ "]"; + "]";
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment