From 97adb5c1b3948a71bd62f50ec5b9a9b37061eeb8 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 2 Apr 2014 17:13:40 +0100 Subject: [PATCH] Ensure ddl-auto=none for non-embedded database A more thorough check is needed to avoid the false assumption that the DataSource is embedded just because an embedded database is on the classpath. You really have to try and look in the connection metadata, so that's what we now do. Fixes gh-621, fixes gh-373 --- .../jdbc/EmbeddedDatabaseConnection.java | 48 ++++++++++ .../jpa/HibernateJpaAutoConfiguration.java | 25 ++---- ...tomHibernateJpaAutoConfigurationTests.java | 88 +++++++++++++++++++ spring-boot-docs/src/main/asciidoc/howto.adoc | 13 ++- .../main/asciidoc/spring-boot-features.adoc | 9 +- 5 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDatabaseConnection.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDatabaseConnection.java index 8f53d84fbc7..d8c04948788 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDatabaseConnection.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDatabaseConnection.java @@ -16,6 +16,14 @@ package org.springframework.boot.autoconfigure.jdbc; +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.util.ClassUtils; @@ -103,6 +111,46 @@ public enum EmbeddedDatabaseConnection { .equals(DERBY.driverClass)); } + /** + * Convenience method to determine if a given data source represents an embedded + * database type. + * + * @param dataSource the data source to interrogate + * @return true if the data sourceis one of the embedded types + */ + public static boolean isEmbedded(DataSource dataSource) { + boolean embedded = false; + try { + embedded = new JdbcTemplate(dataSource) + .execute(new ConnectionCallback() { + @Override + public Boolean doInConnection(Connection con) + throws SQLException, DataAccessException { + String productName = con.getMetaData() + .getDatabaseProductName(); + if (productName == null) { + return false; + } + productName = productName.toUpperCase(); + if (productName.contains(H2.name())) { + return true; + } + if (productName.contains(HSQL.name())) { + return true; + } + if (productName.contains(DERBY.name())) { + return true; + } + return false; + } + }); + } + catch (DataAccessException e) { + // Could not connect, which means it's not embedded + } + return embedded; + } + /** * Returns the most suitable {@link EmbeddedDatabaseConnection} for the given class * loader. 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 3a419a5ba7f..9fb3f14049c 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 @@ -19,8 +19,8 @@ package org.springframework.boot.autoconfigure.orm.jpa; import java.util.Map; import javax.persistence.EntityManager; +import javax.sql.DataSource; -import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; @@ -52,22 +52,14 @@ import org.springframework.util.ClassUtils; EnableTransactionManagement.class, EntityManager.class }) @Conditional(HibernateEntityManagerCondition.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) -public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implements - BeanClassLoaderAware { +public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { private RelaxedPropertyResolver environment; - private ClassLoader classLoader; - public HibernateJpaAutoConfiguration() { this.environment = null; } - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } - @Override public void setEnvironment(Environment environment) { super.setEnvironment(environment); @@ -86,19 +78,18 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implemen Map properties = entityManagerFactoryBean.getJpaPropertyMap(); properties.put("hibernate.ejb.naming_strategy", this.environment.getProperty( "naming-strategy", SpringNamingStrategy.class.getName())); - String ddlAuto = this.environment.getProperty("ddl-auto", getDefaultDdlAuto()); + String ddlAuto = this.environment.getProperty("ddl-auto", + getDefaultDdlAuto(entityManagerFactoryBean.getDataSource())); if (!"none".equals(ddlAuto)) { properties.put("hibernate.hbm2ddl.auto", ddlAuto); } } - private String getDefaultDdlAuto() { - EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection - .get(this.classLoader); - if (embeddedDatabaseConnection == EmbeddedDatabaseConnection.NONE) { - return "none"; + private String getDefaultDdlAuto(DataSource dataSource) { + if (EmbeddedDatabaseConnection.isEmbedded(dataSource)) { + return "create-drop"; } - return "create-drop"; + return "none"; } static class HibernateEntityManagerCondition extends SpringBootCondition { 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 new file mode 100644 index 00000000000..dd232df8efb --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java @@ -0,0 +1,88 @@ +/* + * 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.orm.jpa; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.test.City; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link HibernateJpaAutoConfiguration}. + * + * @author Dave Syer + * @author Phillip Webb + */ +public class CustomHibernateJpaAutoConfigurationTests { + + protected AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void close() { + 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... + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.driverClassName:com.mysql.jdbc.Driver", + "spring.datasource.url:jdbc:mysql://localhost/nonexistent", + "spring.datasource.initialize:false", "spring.jpa.database:MYSQL"); + this.context.register(TestConfiguration.class, DataSourceAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + HibernateJpaAutoConfiguration.class); + this.context.refresh(); + LocalContainerEntityManagerFactoryBean bean = this.context + .getBean(LocalContainerEntityManagerFactoryBean.class); + String actual = (String) bean.getJpaPropertyMap().get("hibernate.hbm2ddl.auto"); + // No default (let Hibernate choose) + assertThat(actual, equalTo(null)); + } + + @Test + public void testDefaultDdlAutoForEmbedded() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false"); + this.context.register(TestConfiguration.class, + EmbeddedDataSourceConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + HibernateJpaAutoConfiguration.class); + this.context.refresh(); + LocalContainerEntityManagerFactoryBean bean = this.context + .getBean(LocalContainerEntityManagerFactoryBean.class); + String actual = (String) bean.getJpaPropertyMap().get("hibernate.hbm2ddl.auto"); + assertThat(actual, equalTo("create-drop")); + } + + @Configuration + @TestAutoConfigurationPackage(City.class) + protected static class TestConfiguration { + + } +} diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 3c673c98926..f9dfd7bb71e 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -935,13 +935,22 @@ and {sc-spring-boot-autoconfigure}/orm/jpa/JpaBaseConfiguration.{sc-ext}[`JpaBas for more details. +[[howto-use-custom-entity-manager]] +=== Use a custom EntityManagerFactory +To take full control of the configuration of the +`EntityManagerFactory`, you need to add a `@Bean` named +"entityManagerFactory". To avoid eager initialization of JPA +infrastructure Spring Boot autoconfiguration does not switch on its +entity manager based on the presence of a bean of that type. Instead +it has to do it by name. + [[howto-use-traditional-persistence-xml]] === Use a traditional persistence.xml Spring doesn't require the use of XML to configure the JPA provider, and Spring Boot assumes you want to take advantage of that feature. If you prefer to use `persistence.xml` -then you need to define your own `@Bean` of type `LocalEntityManagerFactoryBean`, and set -the persistence unit name there. +then you need to define your own `@Bean` of type `LocalEntityManagerFactoryBean` (with +id "entityManagerFactory", and set the persistence unit name there. See https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java[`JpaBaseConfiguration`] diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 660fa19343b..a11de9133fd 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1189,7 +1189,6 @@ See the '<