Commit e032b673 authored by Phillip Webb's avatar Phillip Webb

Polish DataSourceInitialization code

Rename a few classes and methods relating to DataSourceInitialization
and update the DataSourceInitializedPublisher to check for Hibernate
settings.
parent 38af1c2e
......@@ -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
......
/*
* 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);
}
}
/*
* 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.
......@@ -18,7 +18,6 @@ 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;
......@@ -27,143 +26,113 @@ 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;
/**
* 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
* @since 1.1
* @author Phillip Webb
* @since 1.1.0
* @see DataSourceAutoConfiguration
*/
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceInitialization {
private static Log logger = LogFactory.getLog(DataSourceInitialization.class);
class DataSourceInitializer implements ApplicationListener<DataSourceInitializedEvent> {
@Autowired(required=false)
private DataSource dataSource;
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;
@Bean
public ApplicationListener<DataSourceInitializedEvent> dataSourceInitializedListener() {
return new DataSourceInitializedListener();
@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() {
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)) {
List<Resource> scripts = getScripts(this.properties.getSchema(), "schema");
if (!scripts.isEmpty()) {
runScripts(scripts);
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);
@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 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);
}
private void runDataScripts() {
List<Resource> scripts = getScripts(this.properties.getData(), "data");
runScripts(scripts);
}
populator.setSeparator(this.properties.getSeparator());
if (exists) {
DatabasePopulatorUtils.execute(populator, this.dataSource);
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 exists;
return getResources(locations);
}
private List<Resource> getSchemaResources(String schema) {
private List<Resource> getResources(String locations) {
List<Resource> resources = new ArrayList<Resource>();
for (String schemaLocation : StringUtils.commaDelimitedListToStringArray(schema)) {
for (String location : StringUtils.commaDelimitedListToStringArray(locations)) {
try {
resources.addAll(Arrays.asList(this.applicationContext
.getResources(schemaLocation)));
for (Resource resource : this.applicationContext.getResources(location)) {
if (resource.exists()) {
resources.add(resource);
}
}
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load resource from "
+ schemaLocation, ex);
+ location, 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)");
private void runScripts(List<Resource> resources) {
if (resources.isEmpty()) {
return;
}
runSchemaScripts();
}
@Override
public void onApplicationEvent(DataSourceInitializedEvent event) {
runDataScripts();
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);
}
}
/*
* 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);
}
}
}
}
......@@ -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);
}
}
}
}
......@@ -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);
}
}
}
......@@ -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;
/**
* @author Dave Syer
* 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);
}
......
......@@ -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());
}
......
......@@ -1165,7 +1165,9 @@ and `data-${platform}.sql` files (if present), where
it to the vendor name of the database (`hsqldb`, `h2`, `oracle`, `mysql`,
`postgresql` etc.). Spring Boot enables the failfast feature of the Spring JDBC
initializer by default, so if the scripts cause exceptions the application will fail
to start. The script locations can be changed by setting `spring.datasource.schema` and `spring.datasource.data`, and neither location will be processed if `spring.datasource.initialize=false`.
to start. The script locations can be changed by setting `spring.datasource.schema` and
`spring.datasource.data`, and neither location will be processed if
`spring.datasource.initialize=false`.
To disable the failfast you can set `spring.datasource.continueOnError=true`. This can be
useful once an application has matured and been deployed a few times, since the scripts
......
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