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:
parent
2cc5bdfa09
commit
f2e3d94fa1
|
@ -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 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 = {
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue