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
This commit is contained in:
Dave Syer 2014-05-18 09:51:35 +01:00
parent 2cc5bdfa09
commit f2e3d94fa1
6 changed files with 98 additions and 47 deletions

View File

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

View File

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

View File

@ -16,8 +16,14 @@
package org.springframework.boot.autoconfigure.orm.jpa;
import javax.persistence.EntityManager;
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<ContextRefreshedEvent> {
@Autowired
private JpaProperties properties;
@Autowired
private DataSource dataSource;
@Autowired
private BeanFactory beanFactory;
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Override
protected Map<String, Object> getVendorProperties() {
return this.properties.getInitialHibernateProperties(this.dataSource);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Map<String, Object> 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 = {

View File

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

View File

@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> getAdditionalProperties(Map<String, Object> existing,
DataSource dataSource) {
private Map<String, Object> getDeferredAdditionalProperties(
Map<String, Object> properties, DataSource dataSource) {
Map<String, Object> deferred = getAdditionalProperties(properties);
deferred.put("hibernate.hbm2ddl.auto",
getDeferredDdlAuto(properties, dataSource));
return deferred;
}
private Map<String, Object> getAdditionalProperties(Map<String, Object> existing) {
Map<String, Object> result = new HashMap<String, Object>();
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;
}

View File

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