Merge pull request #7796 from eddumelendez:gh-7708

* pr/7796:
  Polish contribution
  Autodetect spring.jpa.database from spring.datasource.url
pull/7866/head
Stephane Nicoll 8 years ago
commit ce9beefc9a

@ -0,0 +1,73 @@
/*
* Copyright 2012-2016 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.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.orm.jpa.vendor.Database;
/**
* Mapper between {@link Database} and {@link DatabaseDriver}.
*
* @author Eddú Meléndez
*/
enum DatabasePlatform {
DB2(Database.DB2, DatabaseDriver.DB2),
DERBY(Database.DERBY, DatabaseDriver.DERBY),
H2(Database.H2, DatabaseDriver.H2),
HSQL(Database.HSQL, DatabaseDriver.HSQLDB),
INFORMIX(Database.INFORMIX, DatabaseDriver.INFORMIX),
MYSQL(Database.MYSQL, DatabaseDriver.MYSQL),
ORACLE(Database.ORACLE, DatabaseDriver.ORACLE),
POSTGRESQL(Database.POSTGRESQL, DatabaseDriver.POSTGRESQL),
SQL_SERVER(Database.SQL_SERVER, DatabaseDriver.SQLSERVER);
private final Database database;
private final DatabaseDriver driver;
DatabasePlatform(Database database, DatabaseDriver driver) {
this.database = database;
this.driver = driver;
}
public Database getDatabase() {
return this.database;
}
public DatabaseDriver getDriver() {
return this.driver;
}
public static DatabasePlatform fromDatabaseDriver(DatabaseDriver driver) {
for (DatabasePlatform mapper : values()) {
if (mapper.getDriver() == driver) {
return mapper;
}
}
return null;
}
}

@ -61,6 +61,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
* @author Oliver Gierke * @author Oliver Gierke
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Kazuki Shimizu * @author Kazuki Shimizu
* @author Eddú Meléndez
*/ */
@EnableConfigurationProperties(JpaProperties.class) @EnableConfigurationProperties(JpaProperties.class)
@Import(DataSourceInitializedPublisher.Registrar.class) @Import(DataSourceInitializedPublisher.Registrar.class)
@ -101,7 +102,7 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
public JpaVendorAdapter jpaVendorAdapter() { public JpaVendorAdapter jpaVendorAdapter() {
AbstractJpaVendorAdapter adapter = createJpaVendorAdapter(); AbstractJpaVendorAdapter adapter = createJpaVendorAdapter();
adapter.setShowSql(this.properties.isShowSql()); adapter.setShowSql(this.properties.isShowSql());
adapter.setDatabase(this.properties.getDatabase()); adapter.setDatabase(this.properties.determineDatabase(this.dataSource));
adapter.setDatabasePlatform(this.properties.getDatabasePlatform()); adapter.setDatabasePlatform(this.properties.getDatabasePlatform());
adapter.setGenerateDdl(this.properties.isGenerateDdl()); adapter.setGenerateDdl(this.properties.isGenerateDdl());
return adapter; return adapter;

@ -21,9 +21,15 @@ import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.orm.jpa.vendor.Database; import org.springframework.orm.jpa.vendor.Database;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -33,11 +39,14 @@ import org.springframework.util.StringUtils;
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Eddú Meléndez
* @since 1.1.0 * @since 1.1.0
*/ */
@ConfigurationProperties(prefix = "spring.jpa") @ConfigurationProperties(prefix = "spring.jpa")
public class JpaProperties { public class JpaProperties {
private static final Log logger = LogFactory.getLog(JpaProperties.class);
/** /**
* Additional native properties to set on the JPA provider. * Additional native properties to set on the JPA provider.
*/ */
@ -53,7 +62,7 @@ public class JpaProperties {
* Target database to operate on, auto-detected by default. Can be alternatively set * Target database to operate on, auto-detected by default. Can be alternatively set
* using the "databasePlatform" property. * using the "databasePlatform" property.
*/ */
private Database database = Database.DEFAULT; private Database database;
/** /**
* Initialize the schema on startup. * Initialize the schema on startup.
@ -125,6 +134,31 @@ public class JpaProperties {
return this.hibernate.getAdditionalProperties(this.properties, dataSource); return this.hibernate.getAdditionalProperties(this.properties, dataSource);
} }
/**
* Determine the {@link Database} to use based on this configuration and the primary
* {@link DataSource}.
* @param dataSource the auto-configured data source
* @return {@code Database}
*/
public Database determineDatabase(DataSource dataSource) {
if (this.database != null) {
return this.database;
}
try {
String jdbcUrl = (String) JdbcUtils.extractDatabaseMetaData(dataSource,
"getURL");
DatabasePlatform databasePlatform = DatabasePlatform.fromDatabaseDriver(
DatabaseDriver.fromJdbcUrl(jdbcUrl));
if (databasePlatform != null) {
return databasePlatform.getDatabase();
}
}
catch (MetaDataAccessException ex) {
logger.warn("Unable to determine jdbc url from datasource", ex);
}
return Database.DEFAULT;
}
public static class Hibernate { public static class Hibernate {
private static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id." private static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id."

@ -16,6 +16,9 @@
package org.springframework.boot.autoconfigure.orm.jpa; package org.springframework.boot.autoconfigure.orm.jpa;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -30,15 +33,22 @@ import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfigurati
import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.autoconfigure.orm.jpa.test.City;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.mock;
/** /**
* Tests for {@link HibernateJpaAutoConfiguration}. * Tests for {@link HibernateJpaAutoConfiguration}.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Eddú Meléndez
*/ */
public class CustomHibernateJpaAutoConfigurationTests { public class CustomHibernateJpaAutoConfigurationTests {
@ -54,7 +64,7 @@ public class CustomHibernateJpaAutoConfigurationTests {
// Set up environment so we get a MySQL database but don't require server to be // Set up environment so we get a MySQL database but don't require server to be
// running... // running...
EnvironmentTestUtils.addEnvironment(this.context, EnvironmentTestUtils.addEnvironment(this.context,
"spring.datasource.driverClassName:com.mysql.jdbc.Driver", "spring.datasource.database:mysql",
"spring.datasource.url:jdbc:mysql://localhost/nonexistent", "spring.datasource.url:jdbc:mysql://localhost/nonexistent",
"spring.datasource.initialize:false", "spring.jpa.database:MYSQL"); "spring.datasource.initialize:false", "spring.jpa.database:MYSQL");
this.context.register(TestConfiguration.class, DataSourceAutoConfiguration.class, this.context.register(TestConfiguration.class, DataSourceAutoConfiguration.class,
@ -101,10 +111,43 @@ public class CustomHibernateJpaAutoConfigurationTests {
assertThat(hibernateProperties.get("hibernate.ejb.naming_strategy")).isNull(); assertThat(hibernateProperties.get("hibernate.ejb.naming_strategy")).isNull();
} }
@Test
public void testDefaultDatabaseForH2() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"spring.datasource.url:jdbc:h2:mem:testdb",
"spring.datasource.initialize:false");
this.context.register(TestConfiguration.class, DataSourceAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
HibernateJpaAutoConfiguration.class);
this.context.refresh();
HibernateJpaVendorAdapter bean = this.context.getBean(HibernateJpaVendorAdapter.class);
Database database = (Database) ReflectionTestUtils.getField(bean, "database");
assertThat(database).isEqualTo(Database.H2);
}
@Configuration @Configuration
@TestAutoConfigurationPackage(City.class) @TestAutoConfigurationPackage(City.class)
protected static class TestConfiguration { protected static class TestConfiguration {
} }
@Configuration
protected static class MockDataSourceConfiguration {
@Bean
public DataSource dataSource() {
DataSource dataSource = mock(DataSource.class);
try {
given(dataSource.getConnection()).willReturn(mock(Connection.class));
given(dataSource.getConnection().getMetaData()).willReturn(
mock(DatabaseMetaData.class));
}
catch (SQLException e) {
//Do nothing
}
return dataSource;
}
}
} }

@ -0,0 +1,86 @@
/*
* Copyright 2012-2016 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.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.orm.jpa.vendor.Database;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DatabasePlatform}.
*
* @author Eddú Meléndez
*/
public class DatabasePlatformTests {
@Test
public void databaseDriverLookups() {
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.DB2))
.isEqualTo(DatabasePlatform.DB2);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.DERBY))
.isEqualTo(DatabasePlatform.DERBY);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.H2))
.isEqualTo(DatabasePlatform.H2);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.HSQLDB))
.isEqualTo(DatabasePlatform.HSQL);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.INFORMIX))
.isEqualTo(DatabasePlatform.INFORMIX);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.MYSQL))
.isEqualTo(DatabasePlatform.MYSQL);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.ORACLE))
.isEqualTo(DatabasePlatform.ORACLE);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.POSTGRESQL))
.isEqualTo(DatabasePlatform.POSTGRESQL);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.SQLSERVER))
.isEqualTo(DatabasePlatform.SQL_SERVER);
}
@Test
public void databaseLookups() {
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.DB2)
.getDatabase())
.isEqualTo(Database.DB2);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.DERBY)
.getDatabase())
.isEqualTo(Database.DERBY);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.H2)
.getDatabase())
.isEqualTo(Database.H2);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.HSQLDB)
.getDatabase())
.isEqualTo(Database.HSQL);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.INFORMIX)
.getDatabase())
.isEqualTo(Database.INFORMIX);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.MYSQL)
.getDatabase())
.isEqualTo(Database.MYSQL);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.ORACLE)
.getDatabase())
.isEqualTo(Database.ORACLE);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.POSTGRESQL)
.getDatabase())
.isEqualTo(Database.POSTGRESQL);
assertThat(DatabasePlatform.fromDatabaseDriver(DatabaseDriver.SQLSERVER)
.getDatabase())
.isEqualTo(Database.SQL_SERVER);
}
}

@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.orm.jpa; package org.springframework.boot.autoconfigure.orm.jpa;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Map; import java.util.Map;
@ -32,11 +34,14 @@ import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.vendor.Database;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.entry;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/** /**
* Tests for {@link JpaProperties}. * Tests for {@link JpaProperties}.
@ -166,6 +171,40 @@ public class JpaPropertiesTests {
.containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true"); .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true");
} }
@Test
public void determineDatabaseNoCheckIfDatabaseIsSet() throws SQLException {
JpaProperties properties = load(HibernateVersion.V5,
"spring.jpa.database=postgresql");
DataSource dataSource = mockStandaloneDataSource();
Database database = properties.determineDatabase(dataSource);
assertThat(database).isEqualTo(Database.POSTGRESQL);
verify(dataSource, never()).getConnection();
}
@Test
public void determineDatabaseWithKnownUrl() {
JpaProperties properties = load(HibernateVersion.V5);
Database database = properties.determineDatabase(
mockDataSource("jdbc:h2:mem:testdb"));
assertThat(database).isEqualTo(Database.H2);
}
@Test
public void determineDatabaseWithKnownUrlAndUserConfig() {
JpaProperties properties = load(HibernateVersion.V5, "spring.jpa.database=mysql");
Database database = properties.determineDatabase(
mockDataSource("jdbc:h2:mem:testdb"));
assertThat(database).isEqualTo(Database.MYSQL);
}
@Test
public void determineDatabaseWithUnknownUrl() {
JpaProperties properties = load(HibernateVersion.V5);
Database database = properties.determineDatabase(
mockDataSource("jdbc:unknown://localhost"));
assertThat(database).isEqualTo(Database.DEFAULT);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private DataSource mockStandaloneDataSource() throws SQLException { private DataSource mockStandaloneDataSource() throws SQLException {
DataSource ds = mock(DataSource.class); DataSource ds = mock(DataSource.class);
@ -173,6 +212,21 @@ public class JpaPropertiesTests {
return ds; return ds;
} }
private DataSource mockDataSource(String jdbcUrl) {
DataSource ds = mock(DataSource.class);
try {
DatabaseMetaData metadata = mock(DatabaseMetaData.class);
given(metadata.getURL()).willReturn(jdbcUrl);
Connection connection = mock(Connection.class);
given(connection.getMetaData()).willReturn(metadata);
given(ds.getConnection()).willReturn(connection);
}
catch (SQLException e) {
//Do nothing
}
return ds;
}
private JpaProperties load(HibernateVersion hibernateVersion, String... environment) { private JpaProperties load(HibernateVersion hibernateVersion, String... environment) {
HibernateVersion.setRunning(hibernateVersion); HibernateVersion.setRunning(hibernateVersion);
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

@ -1797,20 +1797,25 @@ annotation, e.g.
=== Configure JPA properties === Configure JPA properties
Spring Data JPA already provides some vendor-independent configuration options (e.g. Spring Data JPA already provides some vendor-independent configuration options (e.g.
for SQL logging) and Spring Boot exposes those, and a few more for hibernate as external for SQL logging) and Spring Boot exposes those, and a few more for hibernate as external
configuration properties. The most common options to set are: configuration properties. Some of them are automatically detected according to the context
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`).
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.
The most common options to set are:
[indent=0,subs="verbatim,quotes,attributes"] [indent=0,subs="verbatim,quotes,attributes"]
---- ----
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.hibernate.naming.physical-strategy=com.example.MyPhysicalNamingStrategy spring.jpa.hibernate.naming.physical-strategy=com.example.MyPhysicalNamingStrategy
spring.jpa.database=H2
spring.jpa.show-sql=true spring.jpa.show-sql=true
---- ----
The `ddl-auto` setting is a special case in that it has different defaults depending on In addition all properties in `+spring.jpa.properties.*+` are passed through as normal JPA
whether you are using an embedded database (`create-drop`) or not (`none`). In addition properties (with the prefix stripped) when the local `EntityManagerFactory` is created.
all properties in `+spring.jpa.properties.*+` are passed through as normal JPA properties
(with the prefix stripped) when the local `EntityManagerFactory` is created.
Spring Boot provides a consistent naming strategy regardless of the Hibernate generation Spring Boot provides a consistent naming strategy regardless of the Hibernate generation
that you are using. If you are using Hibernate 4, you can customize it using that you are using. If you are using Hibernate 4, you can customize it using

Loading…
Cancel
Save