From afda0ec1297114d4ee6a0206028746d9566397ef Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 21 Aug 2017 17:48:42 +0200 Subject: [PATCH] Default Hibernate DDL auto to none with Flyway/Liquibase This commit adds a strategy interface to specific if a given DataSource has its schema managed. The Hibernate auto-configuration uses it to set it to "none" if a mechanism to initialize the DataSource is found and "create-drop" otherwise. Both Flyway and Liquibase implements that strategy interface and register it in the context accordingly. Closes gh-9262 --- .../flyway/FlywayAutoConfiguration.java | 7 ++ .../FlywaySchemaManagementProvider.java | 52 +++++++++ .../liquibase/LiquibaseAutoConfiguration.java | 9 ++ .../LiquibaseSchemaManagementProvider.java | 52 +++++++++ .../jpa/DataSourceInitializedPublisher.java | 7 +- .../jpa/HibernateDefaultDdlAutoProvider.java | 64 +++++++++++ .../jpa/HibernateJpaAutoConfiguration.java | 14 ++- .../autoconfigure/orm/jpa/JpaProperties.java | 29 ++--- .../flyway/FlywayAutoConfigurationTests.java | 14 +++ ...tomHibernateJpaAutoConfigurationTests.java | 41 +------ .../HibernateDefaultDdlAutoProviderTests.java | 104 ++++++++++++++++++ .../HibernateJpaAutoConfigurationTests.java | 7 ++ .../orm/jpa/JpaPropertiesTests.java | 10 +- .../appendix-application-properties.adoc | 2 +- spring-boot-docs/src/main/asciidoc/howto.adoc | 18 +-- .../src/main/resources/application.properties | 2 - .../boot/jdbc/SchemaManagement.java | 37 +++++++ .../boot/jdbc/SchemaManagementProvider.java | 36 ++++++ 18 files changed, 427 insertions(+), 78 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywaySchemaManagementProvider.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseSchemaManagementProvider.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateDefaultDdlAutoProvider.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateDefaultDdlAutoProviderTests.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jdbc/SchemaManagement.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jdbc/SchemaManagementProvider.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index 71f0036afd..7fdb866536 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -80,6 +80,13 @@ public class FlywayAutoConfiguration { return new StringOrNumberToMigrationVersionConverter(); } + @Bean + public FlywaySchemaManagementProvider flywayDefaultDdlModeProvider( + ObjectProvider> flyways) { + return new FlywaySchemaManagementProvider( + flyways.getIfAvailable(Collections::emptyList)); + } + @Configuration @ConditionalOnMissingBean(Flyway.class) @EnableConfigurationProperties(FlywayProperties.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywaySchemaManagementProvider.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywaySchemaManagementProvider.java new file mode 100644 index 0000000000..42d9611900 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywaySchemaManagementProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2017 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.flyway; + +import java.util.List; + +import javax.sql.DataSource; + +import org.flywaydb.core.Flyway; + +import org.springframework.boot.jdbc.SchemaManagement; +import org.springframework.boot.jdbc.SchemaManagementProvider; + +/** + * A Flyway {@link SchemaManagementProvider} that determines if the schema is managed by + * looking at available {@link Flyway} instances. + * + * @author Stephane Nicoll + */ +class FlywaySchemaManagementProvider implements SchemaManagementProvider { + + private final List flyways; + + FlywaySchemaManagementProvider(List flyways) { + this.flyways = flyways; + } + + @Override + public SchemaManagement getSchemaManagement(DataSource dataSource) { + for (Flyway flyway : this.flyways) { + if (dataSource.equals(flyway.getDataSource())) { + return SchemaManagement.MANAGED; + } + } + return SchemaManagement.UNMANAGED; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java index 40ebf2b3b0..ab08af57ec 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java @@ -17,6 +17,8 @@ package org.springframework.boot.autoconfigure.liquibase; import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; import javax.annotation.PostConstruct; import javax.persistence.EntityManagerFactory; @@ -65,6 +67,13 @@ import org.springframework.util.ReflectionUtils; HibernateJpaAutoConfiguration.class }) public class LiquibaseAutoConfiguration { + @Bean + public LiquibaseSchemaManagementProvider liquibaseDefaultDdlModeProvider( + ObjectProvider> liquibases) { + return new LiquibaseSchemaManagementProvider( + liquibases.getIfAvailable(Collections::emptyList)); + } + @Configuration @ConditionalOnMissingBean(SpringLiquibase.class) @EnableConfigurationProperties(LiquibaseProperties.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseSchemaManagementProvider.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseSchemaManagementProvider.java new file mode 100644 index 0000000000..338a75afeb --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseSchemaManagementProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2017 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.liquibase; + +import java.util.List; + +import javax.sql.DataSource; + +import liquibase.integration.spring.SpringLiquibase; + +import org.springframework.boot.jdbc.SchemaManagement; +import org.springframework.boot.jdbc.SchemaManagementProvider; + +/** + * A Liquibase {@link SchemaManagementProvider} that determines if the schema is managed + * by looking at available {@link SpringLiquibase} instances. + * + * @author Stephane Nicoll + */ +class LiquibaseSchemaManagementProvider implements SchemaManagementProvider { + + private final List liquibases; + + LiquibaseSchemaManagementProvider(List liquibases) { + this.liquibases = liquibases; + } + + @Override + public SchemaManagement getSchemaManagement(DataSource dataSource) { + for (SpringLiquibase liquibase : this.liquibases) { + if (dataSource.equals(liquibase.getDataSource())) { + return SchemaManagement.MANAGED; + } + } + return SchemaManagement.UNMANAGED; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java index 74810e47a2..a7ad9095bf 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializedEvent; +import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; @@ -89,8 +90,10 @@ class DataSourceInitializedPublisher implements BeanPostProcessor { if (this.properties == null) { return true; // better safe than sorry } - Map hibernate = this.properties - .getHibernateProperties(dataSource); + String defaultDdlAuto = (EmbeddedDatabaseConnection.isEmbedded(dataSource) + ? "create-drop" : "none"); + Map hibernate = this.properties.getHibernateProperties( + defaultDdlAuto); if (hibernate.containsKey("hibernate.hbm2ddl.auto")) { return true; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateDefaultDdlAutoProvider.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateDefaultDdlAutoProvider.java new file mode 100644 index 0000000000..f5ea376563 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateDefaultDdlAutoProvider.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2017 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.orm.jpa; + +import java.util.List; + +import javax.sql.DataSource; + +import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.jdbc.SchemaManagement; +import org.springframework.boot.jdbc.SchemaManagementProvider; + +/** + * A {@link SchemaManagementProvider} that invokes a configurable number of + * {@link SchemaManagementProvider} instances for embedded data sources only. + * + * @author Stephane Nicoll + */ +class HibernateDefaultDdlAutoProvider implements SchemaManagementProvider { + + private final List providers; + + HibernateDefaultDdlAutoProvider(List providers) { + this.providers = providers; + } + + public String getDefaultDdlAuto(DataSource dataSource) { + if (!EmbeddedDatabaseConnection.isEmbedded(dataSource)) { + return "none"; + } + SchemaManagement schemaManagement = getSchemaManagement(dataSource); + if (SchemaManagement.MANAGED.equals(schemaManagement)) { + return "none"; + } + return "create-drop"; + + } + + @Override + public SchemaManagement getSchemaManagement(DataSource dataSource) { + for (SchemaManagementProvider provider : this.providers) { + SchemaManagement schemaManagement = provider.getSchemaManagement(dataSource); + if (SchemaManagement.MANAGED.equals(schemaManagement)) { + return schemaManagement; + } + } + return SchemaManagement.UNMANAGED; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java index 70b34b1443..0b0e592f0e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java @@ -17,7 +17,9 @@ package org.springframework.boot.autoconfigure.orm.jpa; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import javax.persistence.EntityManager; @@ -37,6 +39,7 @@ 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.boot.autoconfigure.transaction.TransactionManagerCustomizers; +import org.springframework.boot.jdbc.SchemaManagementProvider; import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; @@ -85,12 +88,17 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { "org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform", "org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform", }; + private final HibernateDefaultDdlAutoProvider defaultDdlAutoProvider; + public HibernateJpaAutoConfiguration(DataSource dataSource, JpaProperties jpaProperties, ObjectProvider jtaTransactionManager, - ObjectProvider transactionManagerCustomizers) { + ObjectProvider transactionManagerCustomizers, + ObjectProvider> providers) { super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers); + this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider( + providers.getIfAvailable(Collections::emptyList)); } @Override @@ -101,7 +109,9 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { @Override protected Map getVendorProperties() { Map vendorProperties = new LinkedHashMap<>(); - vendorProperties.putAll(getProperties().getHibernateProperties(getDataSource())); + String defaultDdlMode = this.defaultDdlAutoProvider.getDefaultDdlAuto( + getDataSource()); + vendorProperties.putAll(getProperties().getHibernateProperties(defaultDdlMode)); return vendorProperties; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java index f2f91ea556..bc0282671c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java @@ -22,7 +22,6 @@ import java.util.Map; import javax.sql.DataSource; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.orm.jpa.vendor.Database; import org.springframework.util.StringUtils; @@ -119,11 +118,11 @@ public class JpaProperties { /** * Get configuration properties for the initialization of the main Hibernate * EntityManagerFactory. - * @param dataSource the DataSource in case it is needed to determine the properties + * @param defaultDdlAuto the default DDL auto (can be {@code null}) * @return some Hibernate properties for configuration */ - public Map getHibernateProperties(DataSource dataSource) { - return this.hibernate.getAdditionalProperties(this.properties, dataSource); + public Map getHibernateProperties(String defaultDdlAuto) { + return this.hibernate.getAdditionalProperties(this.properties, defaultDdlAuto); } /** @@ -146,8 +145,8 @@ public class JpaProperties { /** * DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" - * property. Default to "create-drop" when using an embedded database, "none" - * otherwise. + * property. Default to "create-drop" when using an embedded database and no + * schema manager was detected, "none" otherwise. */ private String ddlAuto; @@ -181,11 +180,11 @@ public class JpaProperties { } private Map getAdditionalProperties(Map existing, - DataSource dataSource) { + String defaultDdlAuto) { Map result = new HashMap<>(existing); applyNewIdGeneratorMappings(result); getNaming().applyNamingStrategies(result); - String ddlAuto = getOrDeduceDdlAuto(existing, dataSource); + String ddlAuto = determineDdlAuto(existing, defaultDdlAuto); if (StringUtils.hasText(ddlAuto) && !"none".equals(ddlAuto)) { result.put("hibernate.hbm2ddl.auto", ddlAuto); } @@ -205,10 +204,9 @@ public class JpaProperties { } } - private String getOrDeduceDdlAuto(Map existing, - DataSource dataSource) { - String ddlAuto = (this.ddlAuto != null ? this.ddlAuto - : getDefaultDdlAuto(dataSource)); + private String determineDdlAuto(Map existing, + String defaultDdlAuto) { + String ddlAuto = (this.ddlAuto != null ? this.ddlAuto : defaultDdlAuto); if (!existing.containsKey("hibernate." + "hbm2ddl.auto") && !"none".equals(ddlAuto)) { return ddlAuto; @@ -219,13 +217,6 @@ public class JpaProperties { return "none"; } - private String getDefaultDdlAuto(DataSource dataSource) { - if (EmbeddedDatabaseConnection.isEmbedded(dataSource)) { - return "create-drop"; - } - return "none"; - } - } public static class Naming { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index ce7366388d..1100c48491 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -40,6 +40,7 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.jdbc.SchemaManagement; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -114,6 +115,19 @@ public class FlywayAutoConfigurationTests { .isEqualTo(this.context.getBean("flywayDataSource")); } + @Test + public void schemaManagementProviderDetectsDataSource() throws Exception { + registerAndRefresh(FlywayDataSourceConfiguration.class, + EmbeddedDataSourceConfiguration.class, FlywayAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + FlywaySchemaManagementProvider schemaManagementProvider = this.context.getBean( + FlywaySchemaManagementProvider.class); + assertThat(schemaManagementProvider.getSchemaManagement(this.context.getBean( + DataSource.class))).isEqualTo(SchemaManagement.UNMANAGED); + assertThat(schemaManagementProvider.getSchemaManagement(this.context.getBean( + "flywayDataSource", DataSource.class))).isEqualTo(SchemaManagement.MANAGED); + } + @Test public void defaultFlyway() throws Exception { registerAndRefresh(EmbeddedDataSourceConfiguration.class, diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java index 4599d426ad..a63bb665af 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java @@ -59,44 +59,6 @@ public class CustomHibernateJpaAutoConfigurationTests { this.context.close(); } - @Test - public void testDefaultDdlAutoForMySql() throws Exception { - // Set up environment so we get a MySQL database but don't require server to be - // running... - TestPropertyValues - .of("spring.datasource.type:" - + org.apache.tomcat.jdbc.pool.DataSource.class.getName(), - "spring.datasource.database:mysql", - "spring.datasource.url:jdbc:mysql://localhost/nonexistent", - "spring.datasource.initialize:false", "spring.jpa.database:MYSQL") - .applyTo(this.context); - this.context.register(TestConfiguration.class, DataSourceAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - HibernateJpaAutoConfiguration.class); - this.context.refresh(); - JpaProperties bean = this.context.getBean(JpaProperties.class); - DataSource dataSource = this.context.getBean(DataSource.class); - String actual = bean.getHibernateProperties(dataSource) - .get("hibernate.hbm2ddl.auto"); - // Default is generic and safe - assertThat(actual).isNull(); - } - - @Test - public void testDefaultDdlAutoForEmbedded() throws Exception { - TestPropertyValues.of("spring.datasource.initialize:false").applyTo(this.context); - this.context.register(TestConfiguration.class, - EmbeddedDataSourceConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - HibernateJpaAutoConfiguration.class); - this.context.refresh(); - JpaProperties bean = this.context.getBean(JpaProperties.class); - DataSource dataSource = this.context.getBean(DataSource.class); - String actual = bean.getHibernateProperties(dataSource) - .get("hibernate.hbm2ddl.auto"); - assertThat(actual).isEqualTo("create-drop"); - } - @Test public void testNamingStrategyDelegatorTakesPrecedence() { TestPropertyValues @@ -108,8 +70,7 @@ public class CustomHibernateJpaAutoConfigurationTests { HibernateJpaAutoConfiguration.class); this.context.refresh(); JpaProperties bean = this.context.getBean(JpaProperties.class); - DataSource dataSource = this.context.getBean(DataSource.class); - Map hibernateProperties = bean.getHibernateProperties(dataSource); + Map hibernateProperties = bean.getHibernateProperties("create-drop"); assertThat(hibernateProperties.get("hibernate.ejb.naming_strategy")).isNull(); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateDefaultDdlAutoProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateDefaultDdlAutoProviderTests.java new file mode 100644 index 0000000000..e0f7145aff --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateDefaultDdlAutoProviderTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2017 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.orm.jpa; + +import java.util.Collections; + +import javax.sql.DataSource; + +import org.junit.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.SchemaManagement; +import org.springframework.boot.jdbc.SchemaManagementProvider; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HibernateDefaultDdlAutoProvider}. + * + * @author Stephane Nicoll + */ +public class HibernateDefaultDdlAutoProviderTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, + HibernateJpaAutoConfiguration.class)) + .withPropertyValues("spring.datasource.initialize:false"); + + @Test + public void defaultDdlAutoForMysql() { + // Set up environment so we get a MySQL database but don't require server to be + // running... + this.contextRunner.withPropertyValues( + "spring.datasource.type:" + + org.apache.tomcat.jdbc.pool.DataSource.class.getName(), + "spring.datasource.database:mysql", + "spring.datasource.url:jdbc:mysql://localhost/nonexistent", + "spring.jpa.database:MYSQL" + ).run((context) -> { + HibernateDefaultDdlAutoProvider ddlAutoProvider = new HibernateDefaultDdlAutoProvider( + Collections.emptyList()); + assertThat(ddlAutoProvider.getDefaultDdlAuto( + context.getBean(DataSource.class))).isEqualTo("none"); + + }); + } + + @Test + public void defaultDDlAutoForEmbedded() { + this.contextRunner.run((context) -> { + HibernateDefaultDdlAutoProvider ddlAutoProvider = new HibernateDefaultDdlAutoProvider( + Collections.emptyList()); + assertThat(ddlAutoProvider.getDefaultDdlAuto( + context.getBean(DataSource.class))).isEqualTo("create-drop"); + }); + } + + @Test + public void defaultDDlAutoForEmbeddedWithPositiveContributor() { + this.contextRunner.run((context) -> { + DataSource dataSource = context.getBean(DataSource.class); + SchemaManagementProvider provider = mock(SchemaManagementProvider.class); + given(provider.getSchemaManagement(dataSource)) + .willReturn(SchemaManagement.MANAGED); + HibernateDefaultDdlAutoProvider ddlAutoProvider = new HibernateDefaultDdlAutoProvider( + Collections.singletonList(provider)); + assertThat(ddlAutoProvider.getDefaultDdlAuto( + dataSource)).isEqualTo("none"); + }); + } + + @Test + public void defaultDDlAutoForEmbeddedWithNegativeContributor() { + this.contextRunner.run((context) -> { + DataSource dataSource = context.getBean(DataSource.class); + SchemaManagementProvider provider = mock(SchemaManagementProvider.class); + given(provider.getSchemaManagement(dataSource)) + .willReturn(SchemaManagement.UNMANAGED); + HibernateDefaultDdlAutoProvider ddlAutoProvider = new HibernateDefaultDdlAutoProvider( + Collections.singletonList(provider)); + assertThat(ddlAutoProvider.getDefaultDdlAuto( + dataSource)).isEqualTo("create-drop"); + }); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index f6acc11ade..ad6e40fdca 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -101,6 +101,13 @@ public class HibernateJpaAutoConfigurationTests .isTrue(); } + @Test + public void testFlywaySwitchOffDdlAuto() throws Exception { + load(new Class[0], new Class[] { FlywayAutoConfiguration.class }, + "spring.datasource.initialize:false", + "spring.flyway.locations:classpath:db/city"); + } + @Test public void testFlywayPlusValidation() throws Exception { load(new Class[0], new Class[] { FlywayAutoConfiguration.class }, diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java index e534a94a9f..5406732976 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java @@ -62,7 +62,7 @@ public class JpaPropertiesTests { public void noCustomNamingStrategy() throws Exception { JpaProperties properties = load(); Map hibernateProperties = properties - .getHibernateProperties(mockStandaloneDataSource()); + .getHibernateProperties("none"); assertThat(hibernateProperties) .doesNotContainKeys("hibernate.ejb.naming_strategy"); assertThat(hibernateProperties).containsEntry( @@ -79,7 +79,7 @@ public class JpaPropertiesTests { "spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit", "spring.jpa.hibernate.naming.physical-strategy:com.example.Physical"); Map hibernateProperties = properties - .getHibernateProperties(mockStandaloneDataSource()); + .getHibernateProperties("none"); assertThat(hibernateProperties).contains( entry("hibernate.implicit_naming_strategy", "com.example.Implicit"), entry("hibernate.physical_naming_strategy", "com.example.Physical")); @@ -93,7 +93,7 @@ public class JpaPropertiesTests { "spring.jpa.properties.hibernate.implicit_naming_strategy:com.example.Implicit", "spring.jpa.properties.hibernate.physical_naming_strategy:com.example.Physical"); Map hibernateProperties = properties - .getHibernateProperties(mockStandaloneDataSource()); + .getHibernateProperties("none"); // You can override them as we don't provide any default assertThat(hibernateProperties).contains( entry("hibernate.implicit_naming_strategy", "com.example.Implicit"), @@ -106,7 +106,7 @@ public class JpaPropertiesTests { public void useNewIdGeneratorMappingsDefault() throws Exception { JpaProperties properties = load(); Map hibernateProperties = properties - .getHibernateProperties(mockStandaloneDataSource()); + .getHibernateProperties("none"); assertThat(hibernateProperties) .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true"); } @@ -116,7 +116,7 @@ public class JpaPropertiesTests { JpaProperties properties = load( "spring.jpa.hibernate.use-new-id-generator-mappings:false"); Map hibernateProperties = properties - .getHibernateProperties(mockStandaloneDataSource()); + .getHibernateProperties("none"); assertThat(hibernateProperties) .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false"); } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index cc2446169d..0308e1f458 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -740,7 +740,7 @@ content into your application; rather pick only the properties that you need. spring.jpa.database= # Target database to operate on, auto-detected by default. Can be alternatively set using the "databasePlatform" property. spring.jpa.database-platform= # Name of the target database to operate on, auto-detected by default. Can be alternatively set using the "Database" enum. spring.jpa.generate-ddl=false # Initialize the schema on startup. - spring.jpa.hibernate.ddl-auto= # DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Default to "create-drop" when using an embedded database, "none" otherwise. + spring.jpa.hibernate.ddl-auto= # DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Default to "create-drop" when using an embedded database and no schema manager was detected, "none" otherwise. spring.jpa.hibernate.naming.implicit-strategy= # Hibernate 5 implicit naming strategy fully qualified name. spring.jpa.hibernate.naming.physical-strategy= # Hibernate 5 physical naming strategy fully qualified name. spring.jpa.hibernate.use-new-id-generator-mappings= # Use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE. diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index ee25ecf38f..953c5877ab 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -1671,7 +1671,10 @@ configuration properties. Some of them are automatically detected according to t so you shouldn't have to set them. The `spring.jpa.hibernate.ddl-auto` is a special case in that it has different defaults -depending on whether you are using an embedded database (`create-drop`) or not (`none`). +depending on runtime conditions. If an embedded database is used and no schema manager, +such as Liquibase or Flyway, is handling the `DataSource` it then defaults to +`create-drop`. In all other cases it defaults to `none`. + The dialect to use is also automatically detected based on the current `DataSource` but you can set `spring.jpa.database` yourself if you want to be explicit and bypass that check on startup. @@ -1890,12 +1893,13 @@ database. This is controlled through two external properties: === Initialize a database using Hibernate You can set `spring.jpa.hibernate.ddl-auto` explicitly and the standard Hibernate property values are `none`, `validate`, `update`, `create`, `create-drop`. Spring Boot chooses a -default value for you based on whether it thinks your database is embedded (default -`create-drop`) or not (default `none`). An embedded database is detected by looking at the -`Connection` type: `hsqldb`, `h2` and `derby` are embedded, the rest are not. Be careful -when switching from in-memory to a '`real`' database that you don't make assumptions about -the existence of the tables and data in the new platform. You either have to set `ddl-auto` -explicitly, or use one of the other mechanisms to initialize the database. +default value for you based on whether it thinks your database is embedded: default to +`create-drop` if no schema manager has been detected, `none` in all other cases. An +embedded database is detected by looking at the `Connection` type: `hsqldb`, `h2` and +`derby` are embedded, the rest are not. Be careful when switching from in-memory to a +'`real`' database that you don't make assumptions about the existence of the tables and +data in the new platform. You either have to set `ddl-auto` explicitly, or use one of the +other mechanisms to initialize the database. NOTE: You can output the schema creation by enabling the `org.hibernate.SQL` logger. This is done for you automatically if you enable the <>. diff --git a/spring-boot-samples/spring-boot-sample-liquibase/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-liquibase/src/main/resources/application.properties index 42f0f81229..377c41d065 100644 --- a/spring-boot-samples/spring-boot-sample-liquibase/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-liquibase/src/main/resources/application.properties @@ -1,5 +1,3 @@ management.security.enabled=false -spring.jpa.hibernate.ddl-auto=none - spring.h2.console.enabled=true diff --git a/spring-boot/src/main/java/org/springframework/boot/jdbc/SchemaManagement.java b/spring-boot/src/main/java/org/springframework/boot/jdbc/SchemaManagement.java new file mode 100644 index 0000000000..2b31673145 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jdbc/SchemaManagement.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2017 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.jdbc; + +/** + * An enumeration of the available schema management options. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public enum SchemaManagement { + + /** + * The schema is managed and will be created at the appropriate time. + */ + MANAGED, + + /** + * The schema is not managed. + */ + UNMANAGED + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jdbc/SchemaManagementProvider.java b/spring-boot/src/main/java/org/springframework/boot/jdbc/SchemaManagementProvider.java new file mode 100644 index 0000000000..82b988dc5c --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jdbc/SchemaManagementProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2017 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.jdbc; + +import javax.sql.DataSource; + +/** + * Strategy interface to determine the {@link SchemaManagement} of a {@link DataSource}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public interface SchemaManagementProvider { + + /** + * Return the {@link SchemaManagement} for the specified {@link DataSource}. + * @param dataSource the dataSource to handle + * @return the {@link SchemaManagement} for the {@link DataSource}. + */ + SchemaManagement getSchemaManagement(DataSource dataSource); + +}