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
This commit is contained in:
Dave Syer 2014-04-02 17:13:40 +01:00
parent e4c67d6dd8
commit 97adb5c1b3
5 changed files with 162 additions and 21 deletions

View File

@ -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<Boolean>() {
@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.

View File

@ -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<String, Object> 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 {

View File

@ -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 {
}
}

View File

@ -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`]

View File

@ -1189,7 +1189,6 @@ See the '<<howto.adoc#howto-separate-entity-definitions-from-spring-configuratio
how-to.
[[boot-features-spring-data-jpa-repositories]]
==== Spring Data JPA Repositories
Spring Data JPA repositories are interfaces that you can define to access data. JPA
@ -1238,9 +1237,15 @@ following to your `application.properties`.
[indent=0]
----
spring.jpa.hibernate.ddl-auto="create-drop"
spring.jpa.hibernate.ddl-auto=create-drop
----
Note that Hibernate's own internal property name for this (if you
happen to remember it better) is `hibernate.hbm2ddl.auto`. You can set
it, along with other Hibernate native properties, using
`spring.jpa.properties.*` (the prefix is stripped before adding them
to the entity manager). Also relevant:
`spring.jpa.generate-ddl=false` switches off all DDL generation.
[[boot-features-nosql]]