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
pull/896/merge
Dave Syer 11 years ago
parent f2e3d94fa1
commit a5543f18b9

@ -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<String, Object> properties = new HashMap<String, Object>();
private Builder(DataSource dataSource) {
this.dataSource = dataSource;
}
@ -126,8 +140,7 @@ public class EntityManagerFactoryBuilder {
* @return the builder for fluent usage
*/
public Builder properties(Map<String, Object> 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);
}
}

@ -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<ContextRefreshedEvent> {
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<String, Object> map = this.properties.getHibernateProperties(this.dataSource);
if ("none".equals(map.get("hibernate.hbm2ddl.auto"))) {
return;
protected EntityManagerFactoryBeanCallback getVendorCallback() {
final Map<String, Object> 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<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 {

@ -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<String, Object> getVendorProperties();
protected abstract EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback();
protected String[] getPackagesToScan() {
List<String> basePackages = AutoConfigurationPackages.get(this.beanFactory);
return basePackages.toArray(new String[basePackages.size()]);

@ -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<String, Object> 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;
}

@ -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());
}
}

@ -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])

@ -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.

@ -78,7 +78,7 @@ class Person {
@Override
public String toString() {
return "Person [firstName=" + firstName + ", lastname=" + lastName
return "Person [firstName=" + firstName + ", lastName=" + lastName
+ "]";
}
}

Loading…
Cancel
Save