Skip to content

Commit 97adb5c

Browse files
author
Dave Syer
committed
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
1 parent e4c67d6 commit 97adb5c

File tree

5 files changed

+162
-21
lines changed

5 files changed

+162
-21
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/EmbeddedDatabaseConnection.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616

1717
package org.springframework.boot.autoconfigure.jdbc;
1818

19+
import java.sql.Connection;
20+
import java.sql.SQLException;
21+
22+
import javax.sql.DataSource;
23+
24+
import org.springframework.dao.DataAccessException;
25+
import org.springframework.jdbc.core.ConnectionCallback;
26+
import org.springframework.jdbc.core.JdbcTemplate;
1927
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
2028
import org.springframework.util.ClassUtils;
2129

@@ -103,6 +111,46 @@ public static boolean isEmbedded(String driverClass) {
103111
.equals(DERBY.driverClass));
104112
}
105113

114+
/**
115+
* Convenience method to determine if a given data source represents an embedded
116+
* database type.
117+
*
118+
* @param dataSource the data source to interrogate
119+
* @return true if the data sourceis one of the embedded types
120+
*/
121+
public static boolean isEmbedded(DataSource dataSource) {
122+
boolean embedded = false;
123+
try {
124+
embedded = new JdbcTemplate(dataSource)
125+
.execute(new ConnectionCallback<Boolean>() {
126+
@Override
127+
public Boolean doInConnection(Connection con)
128+
throws SQLException, DataAccessException {
129+
String productName = con.getMetaData()
130+
.getDatabaseProductName();
131+
if (productName == null) {
132+
return false;
133+
}
134+
productName = productName.toUpperCase();
135+
if (productName.contains(H2.name())) {
136+
return true;
137+
}
138+
if (productName.contains(HSQL.name())) {
139+
return true;
140+
}
141+
if (productName.contains(DERBY.name())) {
142+
return true;
143+
}
144+
return false;
145+
}
146+
});
147+
}
148+
catch (DataAccessException e) {
149+
// Could not connect, which means it's not embedded
150+
}
151+
return embedded;
152+
}
153+
106154
/**
107155
* Returns the most suitable {@link EmbeddedDatabaseConnection} for the given class
108156
* loader.

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import java.util.Map;
2020

2121
import javax.persistence.EntityManager;
22+
import javax.sql.DataSource;
2223

23-
import org.springframework.beans.factory.BeanClassLoaderAware;
2424
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2525
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2626
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
@@ -52,22 +52,14 @@
5252
EnableTransactionManagement.class, EntityManager.class })
5353
@Conditional(HibernateEntityManagerCondition.class)
5454
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
55-
public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration implements
56-
BeanClassLoaderAware {
55+
public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
5756

5857
private RelaxedPropertyResolver environment;
5958

60-
private ClassLoader classLoader;
61-
6259
public HibernateJpaAutoConfiguration() {
6360
this.environment = null;
6461
}
6562

66-
@Override
67-
public void setBeanClassLoader(ClassLoader classLoader) {
68-
this.classLoader = classLoader;
69-
}
70-
7163
@Override
7264
public void setEnvironment(Environment environment) {
7365
super.setEnvironment(environment);
@@ -86,19 +78,18 @@ protected void configure(
8678
Map<String, Object> properties = entityManagerFactoryBean.getJpaPropertyMap();
8779
properties.put("hibernate.ejb.naming_strategy", this.environment.getProperty(
8880
"naming-strategy", SpringNamingStrategy.class.getName()));
89-
String ddlAuto = this.environment.getProperty("ddl-auto", getDefaultDdlAuto());
81+
String ddlAuto = this.environment.getProperty("ddl-auto",
82+
getDefaultDdlAuto(entityManagerFactoryBean.getDataSource()));
9083
if (!"none".equals(ddlAuto)) {
9184
properties.put("hibernate.hbm2ddl.auto", ddlAuto);
9285
}
9386
}
9487

95-
private String getDefaultDdlAuto() {
96-
EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection
97-
.get(this.classLoader);
98-
if (embeddedDatabaseConnection == EmbeddedDatabaseConnection.NONE) {
99-
return "none";
88+
private String getDefaultDdlAuto(DataSource dataSource) {
89+
if (EmbeddedDatabaseConnection.isEmbedded(dataSource)) {
90+
return "create-drop";
10091
}
101-
return "create-drop";
92+
return "none";
10293
}
10394

10495
static class HibernateEntityManagerCondition extends SpringBootCondition {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.orm.jpa;
18+
19+
import org.junit.After;
20+
import org.junit.Test;
21+
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
22+
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
23+
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
24+
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
25+
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
26+
import org.springframework.boot.test.EnvironmentTestUtils;
27+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
30+
31+
import static org.hamcrest.Matchers.equalTo;
32+
import static org.junit.Assert.assertThat;
33+
34+
/**
35+
* Tests for {@link HibernateJpaAutoConfiguration}.
36+
*
37+
* @author Dave Syer
38+
* @author Phillip Webb
39+
*/
40+
public class CustomHibernateJpaAutoConfigurationTests {
41+
42+
protected AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
43+
44+
@After
45+
public void close() {
46+
this.context.close();
47+
}
48+
49+
@Test
50+
public void testDefaultDdlAutoForMySql() throws Exception {
51+
// Set up environment so we get a MySQL database but don't require server to be
52+
// running...
53+
EnvironmentTestUtils.addEnvironment(this.context,
54+
"spring.datasource.driverClassName:com.mysql.jdbc.Driver",
55+
"spring.datasource.url:jdbc:mysql://localhost/nonexistent",
56+
"spring.datasource.initialize:false", "spring.jpa.database:MYSQL");
57+
this.context.register(TestConfiguration.class, DataSourceAutoConfiguration.class,
58+
PropertyPlaceholderAutoConfiguration.class,
59+
HibernateJpaAutoConfiguration.class);
60+
this.context.refresh();
61+
LocalContainerEntityManagerFactoryBean bean = this.context
62+
.getBean(LocalContainerEntityManagerFactoryBean.class);
63+
String actual = (String) bean.getJpaPropertyMap().get("hibernate.hbm2ddl.auto");
64+
// No default (let Hibernate choose)
65+
assertThat(actual, equalTo(null));
66+
}
67+
68+
@Test
69+
public void testDefaultDdlAutoForEmbedded() throws Exception {
70+
EnvironmentTestUtils.addEnvironment(this.context,
71+
"spring.datasource.initialize:false");
72+
this.context.register(TestConfiguration.class,
73+
EmbeddedDataSourceConfiguration.class,
74+
PropertyPlaceholderAutoConfiguration.class,
75+
HibernateJpaAutoConfiguration.class);
76+
this.context.refresh();
77+
LocalContainerEntityManagerFactoryBean bean = this.context
78+
.getBean(LocalContainerEntityManagerFactoryBean.class);
79+
String actual = (String) bean.getJpaPropertyMap().get("hibernate.hbm2ddl.auto");
80+
assertThat(actual, equalTo("create-drop"));
81+
}
82+
83+
@Configuration
84+
@TestAutoConfigurationPackage(City.class)
85+
protected static class TestConfiguration {
86+
87+
}
88+
}

spring-boot-docs/src/main/asciidoc/howto.adoc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -935,13 +935,22 @@ and {sc-spring-boot-autoconfigure}/orm/jpa/JpaBaseConfiguration.{sc-ext}[`JpaBas
935935
for more details.
936936

937937

938+
[[howto-use-custom-entity-manager]]
939+
=== Use a custom EntityManagerFactory
940+
To take full control of the configuration of the
941+
`EntityManagerFactory`, you need to add a `@Bean` named
942+
"entityManagerFactory". To avoid eager initialization of JPA
943+
infrastructure Spring Boot autoconfiguration does not switch on its
944+
entity manager based on the presence of a bean of that type. Instead
945+
it has to do it by name.
946+
938947

939948
[[howto-use-traditional-persistence-xml]]
940949
=== Use a traditional persistence.xml
941950
Spring doesn't require the use of XML to configure the JPA provider, and Spring Boot
942951
assumes you want to take advantage of that feature. If you prefer to use `persistence.xml`
943-
then you need to define your own `@Bean` of type `LocalEntityManagerFactoryBean`, and set
944-
the persistence unit name there.
952+
then you need to define your own `@Bean` of type `LocalEntityManagerFactoryBean` (with
953+
id "entityManagerFactory", and set the persistence unit name there.
945954

946955
See
947956
https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java[`JpaBaseConfiguration`]

spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,6 @@ See the '<<howto.adoc#howto-separate-entity-definitions-from-spring-configuratio
11891189
how-to.
11901190

11911191

1192-
11931192
[[boot-features-spring-data-jpa-repositories]]
11941193
==== Spring Data JPA Repositories
11951194
Spring Data JPA repositories are interfaces that you can define to access data. JPA
@@ -1238,9 +1237,15 @@ following to your `application.properties`.
12381237

12391238
[indent=0]
12401239
----
1241-
spring.jpa.hibernate.ddl-auto="create-drop"
1240+
spring.jpa.hibernate.ddl-auto=create-drop
12421241
----
12431242

1243+
Note that Hibernate's own internal property name for this (if you
1244+
happen to remember it better) is `hibernate.hbm2ddl.auto`. You can set
1245+
it, along with other Hibernate native properties, using
1246+
`spring.jpa.properties.*` (the prefix is stripped before adding them
1247+
to the entity manager). Also relevant:
1248+
`spring.jpa.generate-ddl=false` switches off all DDL generation.
12441249

12451250

12461251
[[boot-features-nosql]]

0 commit comments

Comments
 (0)