Commit f2e3d94f authored by Dave Syer's avatar Dave Syer

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
parent 2cc5bdfa
......@@ -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;
}
};
}
}
}
......@@ -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;
}
......
......@@ -16,8 +16,14 @@
package org.springframework.boot.autoconfigure.orm.jpa;
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 = {
......
......@@ -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()]);
......
......@@ -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;
}
......
......@@ -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"));
}
......
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