Polish DataSourceInitialization code

Rename a few classes and methods relating to DataSourceInitialization
and update the DataSourceInitializedPublisher to check for Hibernate
settings.
This commit is contained in:
Phillip Webb
2014-06-06 06:55:10 -07:00
parent 38af1c2eb3
commit e032b673a2
10 changed files with 282 additions and 231 deletions

View File

@@ -51,17 +51,21 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
*/
@Configuration
@ConditionalOnClass(EmbeddedDatabaseType.class)
@Import(DataSourceInitialization.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
public static final String CONFIGURATION_PREFIX = "spring.datasource";
@Autowired
private DataSourceProperties properties;
@Autowired(required = false)
private DataSource dataSource;
@Autowired
private DataSourceProperties properties;
@Bean
public DataSourceInitializer dataSourceAutoConfigurationInitializer() {
return new DataSourceInitializer();
}
/**
* Determines if the {@code dataSource} being used by Spring was created from

View File

@@ -1,169 +0,0 @@
/*
* 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.jdbc;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
* @since 1.1
*/
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceInitialization {
private static Log logger = LogFactory.getLog(DataSourceInitialization.class);
@Autowired(required=false)
private DataSource dataSource;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private DataSourceProperties properties;
private boolean initialized = false;
@Bean
public ApplicationListener<DataSourceInitializedEvent> dataSourceInitializedListener() {
return new DataSourceInitializedListener();
}
private void runSchemaScripts() {
String schema = this.properties.getSchema();
if (schema == null) {
String platform = this.properties.getPlatform();
schema = "classpath*:schema-" + platform + ".sql,";
schema += "classpath*:schema.sql";
}
if (runScripts(schema)) {
this.applicationContext.publishEvent(new DataSourceInitializedEvent(
this.dataSource));
}
}
private void runDataScripts() {
if (this.initialized) {
return;
}
String schema = this.properties.getData();
if (schema == null) {
String platform = this.properties.getPlatform();
schema = "classpath*:data-" + platform + ".sql,";
schema += "classpath*:data.sql";
}
runScripts(schema);
this.initialized = true;
}
private boolean runScripts(String scripts) {
if (this.dataSource == null) {
logger.debug("No DataSource found so not initializing");
return false;
}
List<Resource> resources = getSchemaResources(scripts);
boolean continueOnError = this.properties.isContinueOnError();
boolean exists = false;
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
for (Resource resource : resources) {
if (resource.exists()) {
exists = true;
populator.addScript(resource);
populator.setContinueOnError(continueOnError);
}
}
populator.setSeparator(this.properties.getSeparator());
if (exists) {
DatabasePopulatorUtils.execute(populator, this.dataSource);
}
return exists;
}
private List<Resource> getSchemaResources(String schema) {
List<Resource> resources = new ArrayList<Resource>();
for (String schemaLocation : StringUtils.commaDelimitedListToStringArray(schema)) {
try {
resources.addAll(Arrays.asList(this.applicationContext
.getResources(schemaLocation)));
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load resource from "
+ schemaLocation, ex);
}
}
return resources;
}
@SuppressWarnings("serial")
public static class DataSourceInitializedEvent extends ApplicationEvent {
public DataSourceInitializedEvent(DataSource source) {
super(source);
}
}
private class DataSourceInitializedListener implements
ApplicationListener<DataSourceInitializedEvent> {
// Keep this in the nested class so that it doesn't have to be called before the
// listener is instantiated (ordering problems otherwise)
@PostConstruct
protected void initialize() {
boolean initialize = DataSourceInitialization.this.properties.isInitialize();
if (!initialize) {
logger.debug("Initialization disabled (not running DDL scripts)");
return;
}
runSchemaScripts();
}
@Override
public void onApplicationEvent(DataSourceInitializedEvent event) {
runDataScripts();
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2014 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.jdbc;
import javax.sql.DataSource;
import org.springframework.context.ApplicationEvent;
/**
* {@link ApplicationEvent} used internally to trigger {@link DataSource} initialization.
* Initialization can occur when {@literal schema-*.sql} files are executed or when
* external libraries (e.g. JPA) initialize the database.
*
* @author Dave Syer
* @see DataSourceInitializer
* @since 1.1.0
*/
@SuppressWarnings("serial")
public class DataSourceInitializedEvent extends ApplicationEvent {
/**
* Create a new {@link DataSourceInitializedEvent}.
* @param source the source {@link DataSource}.
*/
public DataSourceInitializedEvent(DataSource source) {
super(source);
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2012-2014 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.jdbc;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.StringUtils;
/**
* Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on
* {@link PostConstruct} and and {@literal data-*.sql} SQL scripts on a
* {@link DataSourceInitializedEvent}.
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.1.0
* @see DataSourceAutoConfiguration
*/
class DataSourceInitializer implements ApplicationListener<DataSourceInitializedEvent> {
private static Log logger = LogFactory.getLog(DataSourceInitializer.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired(required = false)
private DataSource dataSource;
@Autowired
private DataSourceProperties properties;
private boolean initialized = false;
@PostConstruct
protected void initialize() {
if (!this.properties.isInitialize()) {
logger.debug("Initialization disabled (not running DDL scripts)");
return;
}
if (this.dataSource == null) {
logger.debug("No DataSource found so not initializing");
return;
}
runSchemaScripts();
}
private void runSchemaScripts() {
List<Resource> scripts = getScripts(this.properties.getSchema(), "schema");
if (!scripts.isEmpty()) {
runScripts(scripts);
this.applicationContext.publishEvent(new DataSourceInitializedEvent(
this.dataSource));
}
}
@Override
public void onApplicationEvent(DataSourceInitializedEvent event) {
// NOTE the even can happen more than once and
// the event datasource if not used here
if (!this.initialized) {
runDataScripts();
this.initialized = true;
}
}
private void runDataScripts() {
List<Resource> scripts = getScripts(this.properties.getData(), "data");
runScripts(scripts);
}
private List<Resource> getScripts(String locations, String fallback) {
if (locations == null) {
String platform = this.properties.getPlatform();
locations = "classpath*:" + fallback + "-" + platform + ".sql,";
locations += "classpath*:" + fallback + ".sql";
}
return getResources(locations);
}
private List<Resource> getResources(String locations) {
List<Resource> resources = new ArrayList<Resource>();
for (String location : StringUtils.commaDelimitedListToStringArray(locations)) {
try {
for (Resource resource : this.applicationContext.getResources(location)) {
if (resource.exists()) {
resources.add(resource);
}
}
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load resource from "
+ location, ex);
}
}
return resources;
}
private void runScripts(List<Resource> resources) {
if (resources.isEmpty()) {
return;
}
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setContinueOnError(this.properties.isContinueOnError());
populator.setSeparator(this.properties.getSeparator());
for (Resource resource : resources) {
populator.addScript(resource);
}
DatabasePopulatorUtils.execute(populator, this.dataSource);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.
@@ -16,21 +16,38 @@
package org.springframework.boot.autoconfigure.orm.jpa;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitialization.DataSourceInitializedEvent;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class DataSourceInitializedPublisher implements BeanPostProcessor {
/**
* {@link BeanPostProcessor} used to fire {@link DataSourceInitializedEvent}s. Should only
* be registered via the inner {@link Registrar} class.
*
* @author Dave Syer
* @since 1.1.0
*/
class DataSourceInitializedPublisher implements BeanPostProcessor {
@Autowired
private ApplicationContext applicationContext;
private DataSource dataSource;
private JpaProperties properties;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
@@ -44,10 +61,48 @@ public class DataSourceInitializedPublisher implements BeanPostProcessor {
// Normally this will be the right DataSource
this.dataSource = (DataSource) bean;
}
if (bean instanceof EntityManagerFactory && this.dataSource != null) {
if (bean instanceof JpaProperties) {
this.properties = (JpaProperties) bean;
}
if (bean instanceof EntityManagerFactory && this.dataSource != null
&& isInitializingDatabase()) {
this.applicationContext.publishEvent(new DataSourceInitializedEvent(
this.dataSource));
}
return bean;
}
}
private boolean isInitializingDatabase() {
Map<String, String> hibernate = this.properties
.getHibernateProperties(this.dataSource);
if (hibernate.containsKey("hibernate.hbm2ddl.auto")) {
return true;
}
return false;
}
/**
* {@link ImportBeanDefinitionRegistrar} to register the
* {@link DataSourceInitializedPublisher} without causing early bean instantiation
* issues.
*/
static class Registrar implements ImportBeanDefinitionRegistrar {
private static final String BEAN_NAME = "dataSourceInitializedPublisher";
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BEAN_NAME)) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DataSourceInitializedPublisher.class);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// We don't need this one to be post processed otherwise it can cause a
// cascade of bean instantiation that we would rather avoid.
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
}
}
}
}

View File

@@ -25,23 +25,17 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.DataSourceInitializedRegistrar;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Primary;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@@ -61,7 +55,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
* @author Oliver Gierke
*/
@EnableConfigurationProperties(JpaProperties.class)
@Import(DataSourceInitializedRegistrar.class)
@Import(DataSourceInitializedPublisher.Registrar.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@@ -155,24 +149,4 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
}
protected static class DataSourceInitializedRegistrar implements
ImportBeanDefinitionRegistrar {
private static final String BEAN_NAME = "dataSourceInitializedPublisher";
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BEAN_NAME)) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DataSourceInitializedPublisher.class);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// We don't need this one to be post processed otherwise it can cause a
// cascade of bean instantiation that we would rather avoid.
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
}
}
}
}

View File

@@ -139,19 +139,6 @@ public class JpaProperties {
return this.ddlAuto;
}
private String getActualDdlAuto(Map<String, String> 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 existing.get("hibernate.hbm2ddl.auto");
}
return "none";
}
public void setDdlAuto(String ddlAuto) {
this.ddlAuto = ddlAuto;
}
@@ -167,7 +154,7 @@ public class JpaProperties {
result.put("hibernate.ejb.naming_strategy",
DEFAULT_NAMING_STRATEGY.getName());
}
String ddlAuto = getActualDdlAuto(existing, dataSource);
String ddlAuto = getOrDeduceDdlAuto(existing, dataSource);
if (StringUtils.hasText(ddlAuto) && !"none".equals(ddlAuto)) {
result.put("hibernate.hbm2ddl.auto", ddlAuto);
}
@@ -177,8 +164,17 @@ public class JpaProperties {
return result;
}
private boolean isAlreadyProvided(Map<String, String> existing, String key) {
return existing.containsKey("hibernate." + key);
private String getOrDeduceDdlAuto(Map<String, String> 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 existing.get("hibernate.hbm2ddl.auto");
}
return "none";
}
private String getDefaultDdlAuto(DataSource dataSource) {
@@ -188,6 +184,10 @@ public class JpaProperties {
return "none";
}
private boolean isAlreadyProvided(Map<String, String> existing, String key) {
return existing.containsKey("hibernate." + key);
}
}
}

View File

@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.jdbc;
import static org.junit.Assert.*;
package org.springframework.boot.autoconfigure.jdbc;
import java.util.Random;
@@ -31,11 +30,16 @@ import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.ClassUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link DataSourceInitializer}.
*
* @author Dave Syer
*
*/
public class DataSourceInitializationTests {
public class DataSourceInitializerTests {
private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@@ -50,15 +54,15 @@ public class DataSourceInitializationTests {
@After
public void restore() {
EmbeddedDatabaseConnection.override = null;
if (context!=null) {
context.close();
if (this.context != null) {
this.context.close();
}
}
@Test
public void testDefaultDataSourceDoesNotExists() throws Exception {
this.context.register(DataSourceInitialization.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.register(DataSourceInitializer.class,
PropertyPlaceholderAutoConfiguration.class, DataSourceProperties.class);
this.context.refresh();
assertEquals(0, this.context.getBeanNamesForType(DataSource.class).length);
}

View File

@@ -30,7 +30,7 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitialization;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
@@ -191,7 +191,7 @@ public abstract class AbstractJpaAutoConfigurationTests {
protected void setupTestConfiguration(Class<?> configClass) {
this.context.register(configClass, EmbeddedDataSourceConfiguration.class,
DataSourceInitialization.class,
DataSourceAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, getAutoConfigureClass());
}