mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Defer SQL initialization to fit with JPA better
Added 2 new spring.datasource.* properties ("data" like "schema", and "deferDdl" like the "spring.jpa.hibernate.*" flag). The SQL scripts are then run separately and the "data" ones are triggered by a new DataSourceInitializedEvent, which is also published by the Hibernate DDL schema export. Fixes gh-1006
This commit is contained in:
parent
efcbb32788
commit
49a09c807c
@ -16,16 +16,8 @@
|
||||
|
||||
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.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -38,23 +30,18 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link DataSource}.
|
||||
@ -64,73 +51,18 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(EmbeddedDatabaseType.class)
|
||||
@Import(DataSourceInitialization.class)
|
||||
@EnableConfigurationProperties(DataSourceProperties.class)
|
||||
public class DataSourceAutoConfiguration {
|
||||
|
||||
private static Log logger = LogFactory.getLog(DataSourceAutoConfiguration.class);
|
||||
|
||||
public static final String CONFIGURATION_PREFIX = "spring.datasource";
|
||||
|
||||
@Autowired(required = false)
|
||||
private DataSource dataSource;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private DataSourceProperties properties;
|
||||
|
||||
@PostConstruct
|
||||
protected void initialize() {
|
||||
boolean initialize = this.properties.isInitialize();
|
||||
if (this.dataSource == null || !initialize) {
|
||||
logger.debug("No DataSource found so not initializing");
|
||||
return;
|
||||
}
|
||||
|
||||
String schema = this.properties.getSchema();
|
||||
if (schema == null) {
|
||||
String platform = this.properties.getPlatform();
|
||||
schema = "classpath*:schema-" + platform + ".sql,";
|
||||
schema += "classpath*:schema.sql,";
|
||||
schema += "classpath*:data-" + platform + ".sql,";
|
||||
schema += "classpath*:data.sql";
|
||||
}
|
||||
|
||||
List<Resource> resources = getSchemaResources(schema);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the {@code dataSource} being used by Spring was created from
|
||||
* {@link EmbeddedDataSourceConfiguration}.
|
||||
|
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.context.event.ContextRefreshedEvent;
|
||||
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
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(DataSourceProperties.class)
|
||||
public class DataSourceInitialization implements
|
||||
ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
private static Log logger = LogFactory.getLog(DataSourceAutoConfiguration.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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
if (this.properties.isDeferDdl()) {
|
||||
boolean initialize = this.properties.isInitialize();
|
||||
if (!initialize) {
|
||||
logger.debug("Initialization disabled (not running DDL scripts)");
|
||||
return;
|
||||
}
|
||||
runSchemaScripts();
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
protected void initialize() {
|
||||
if (!this.properties.isDeferDdl()) {
|
||||
boolean initialize = this.properties.isInitialize();
|
||||
if (!initialize) {
|
||||
logger.debug("Initialization disabled (not running DDL scripts)");
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
public static class DataSourceInitializedEvent extends ApplicationEvent {
|
||||
|
||||
public DataSourceInitializedEvent(DataSource source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class DataSourceInitializedListener implements
|
||||
ApplicationListener<DataSourceInitializedEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(DataSourceInitializedEvent event) {
|
||||
runDataScripts();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -46,10 +46,14 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
|
||||
|
||||
private boolean initialize = true;
|
||||
|
||||
private boolean deferDdl = false;
|
||||
|
||||
private String platform = "all";
|
||||
|
||||
private String schema;
|
||||
|
||||
private String data;
|
||||
|
||||
private boolean continueOnError = false;
|
||||
|
||||
private String separator = ";";
|
||||
@ -154,6 +158,14 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
|
||||
this.initialize = initialize;
|
||||
}
|
||||
|
||||
public void setDeferDdl(boolean deferDdl) {
|
||||
this.deferDdl = deferDdl;
|
||||
}
|
||||
|
||||
public boolean isDeferDdl() {
|
||||
return this.deferDdl;
|
||||
}
|
||||
|
||||
public String getPlatform() {
|
||||
return this.platform;
|
||||
}
|
||||
@ -170,6 +182,14 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public void setData(String script) {
|
||||
this.data = script;
|
||||
}
|
||||
|
||||
public boolean isContinueOnError() {
|
||||
return this.continueOnError;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
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.jdbc.DataSourceInitialization.DataSourceInitializedEvent;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.HibernateEntityManagerCondition;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
@ -90,7 +91,7 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
private static class DeferredSchemaAction implements
|
||||
private class DeferredSchemaAction implements
|
||||
ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
private Map<String, String> map;
|
||||
@ -105,11 +106,14 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
String ddlAuto = this.map.get("hibernate.hbm2ddl.auto");
|
||||
if (ddlAuto == null || "none".equals(ddlAuto)) {
|
||||
if (ddlAuto == null || "none".equals(ddlAuto) || "".equals(ddlAuto)) {
|
||||
return;
|
||||
}
|
||||
Bootstrap.getEntityManagerFactoryBuilder(
|
||||
this.factory.getPersistenceUnitInfo(), this.map).generateSchema();
|
||||
HibernateJpaAutoConfiguration.this.applicationContext
|
||||
.publishEvent(new DataSourceInitializedEvent(
|
||||
HibernateJpaAutoConfiguration.this.dataSource));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +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.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
|
||||
@ -152,6 +153,8 @@ public abstract class AbstractJpaAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void usesManuallyDefinedEntityManagerFactoryBeanIfAvailable() {
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"spring.datasource.initialize:false");
|
||||
setupTestConfiguration(TestConfigurationWithEntityManagerFactory.class);
|
||||
this.context.refresh();
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = this.context
|
||||
@ -188,6 +191,7 @@ public abstract class AbstractJpaAutoConfigurationTests {
|
||||
|
||||
protected void setupTestConfiguration(Class<?> configClass) {
|
||||
this.context.register(configClass, EmbeddedDataSourceConfiguration.class,
|
||||
DataSourceInitialization.class,
|
||||
PropertyPlaceholderAutoConfiguration.class, getAutoConfigureClass());
|
||||
}
|
||||
|
||||
|
@ -16,12 +16,16 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.orm.jpa;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
@ -38,6 +42,30 @@ public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigura
|
||||
return HibernateJpaAutoConfiguration.class;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataScriptWithDdlAuto() throws Exception {
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"spring.datasource.data:classpath:/city.sql",
|
||||
"spring.datasource.schema:classpath:/ddl.sql");
|
||||
setupTestConfiguration();
|
||||
this.context.refresh();
|
||||
assertEquals(new Integer(1),
|
||||
new JdbcTemplate(this.context.getBean(DataSource.class)).queryForObject(
|
||||
"SELECT COUNT(*) from CITY", Integer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataScriptWithDeferredDdl() throws Exception {
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"spring.datasource.data:classpath:/city.sql",
|
||||
"spring.datasource.deferDdl:true");
|
||||
setupTestConfiguration();
|
||||
this.context.refresh();
|
||||
assertEquals(new Integer(1),
|
||||
new JdbcTemplate(this.context.getBean(DataSource.class)).queryForObject(
|
||||
"SELECT COUNT(*) from CITY", Integer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomNamingStrategy() throws Exception {
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
|
1
spring-boot-autoconfigure/src/test/resources/city.sql
Normal file
1
spring-boot-autoconfigure/src/test/resources/city.sql
Normal file
@ -0,0 +1 @@
|
||||
INSERT INTO CITY (NAME, STATE, COUNTRY, MAP) values ('Washington', 'DC', 'US', 'Google');
|
@ -156,7 +156,9 @@ content into your application; rather pick only the properties that you need.
|
||||
# DATASOURCE ({sc-spring-boot-autoconfigure}/jdbc/DataSourceAutoConfiguration.{sc-ext}[DataSourceAutoConfiguration] & {sc-spring-boot-autoconfigure}//jdbc/AbstractDataSourceConfiguration.{sc-ext}[AbstractDataSourceConfiguration])
|
||||
spring.datasource.name= # name of the data source
|
||||
spring.datasource.initialize=true # populate using data.sql
|
||||
spring.datasource.schema= # a schema resource reference
|
||||
spring.datasource.deferDdl= # flag to indicate that schema scripts will run after the application starts (default false)
|
||||
spring.datasource.schema= # a schema (DDL) script resource reference
|
||||
spring.datasource.data= # a data (DML) script resource reference
|
||||
spring.datasource.platform= # the platform to use in the schema resource (schema-${platform}.sql)
|
||||
spring.datasource.continueOnError=false # continue even if can't be initialized
|
||||
spring.datasource.separator=; # statement separator in SQL initialization scripts
|
||||
|
@ -1165,7 +1165,7 @@ 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.
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user