diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatform.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatform.java new file mode 100644 index 0000000000..a10a473d5d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatform.java @@ -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; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java index 3d4452151f..5c5ca4b1be 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java @@ -61,6 +61,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter * @author Oliver Gierke * @author Andy Wilkinson * @author Kazuki Shimizu + * @author Eddú Meléndez */ @EnableConfigurationProperties(JpaProperties.class) @Import(DataSourceInitializedPublisher.Registrar.class) @@ -101,7 +102,7 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { public JpaVendorAdapter jpaVendorAdapter() { AbstractJpaVendorAdapter adapter = createJpaVendorAdapter(); adapter.setShowSql(this.properties.isShowSql()); - adapter.setDatabase(this.properties.getDatabase()); + adapter.setDatabase(this.properties.determineDatabase(this.dataSource)); adapter.setDatabasePlatform(this.properties.getDatabasePlatform()); adapter.setGenerateDdl(this.properties.isGenerateDdl()); return adapter; 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 ecb118b4ff..148ba6cc01 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 @@ -21,9 +21,15 @@ import java.util.Map; 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.context.properties.ConfigurationProperties; 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.util.StringUtils; @@ -33,11 +39,14 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Andy Wilkinson * @author Stephane Nicoll + * @author Eddú Meléndez * @since 1.1.0 */ @ConfigurationProperties(prefix = "spring.jpa") public class JpaProperties { + private static final Log logger = LogFactory.getLog(JpaProperties.class); + /** * 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 * using the "databasePlatform" property. */ - private Database database = Database.DEFAULT; + private Database database; /** * Initialize the schema on startup. @@ -125,6 +134,31 @@ public class JpaProperties { 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 { private static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id." 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 fb5fbf1f0a..208e1e88fb 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 @@ -16,6 +16,9 @@ package org.springframework.boot.autoconfigure.orm.jpa; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; import java.util.Map; 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.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; 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.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.mock; /** * Tests for {@link HibernateJpaAutoConfiguration}. * * @author Dave Syer * @author Phillip Webb + * @author Eddú Meléndez */ 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 // running... EnvironmentTestUtils.addEnvironment(this.context, - "spring.datasource.driverClassName:com.mysql.jdbc.Driver", + "spring.datasource.database:mysql", "spring.datasource.url:jdbc:mysql://localhost/nonexistent", "spring.datasource.initialize:false", "spring.jpa.database:MYSQL"); this.context.register(TestConfiguration.class, DataSourceAutoConfiguration.class, @@ -101,10 +111,43 @@ public class CustomHibernateJpaAutoConfigurationTests { 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 @TestAutoConfigurationPackage(City.class) 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; + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatformTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatformTests.java new file mode 100644 index 0000000000..d91cc6f0b8 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatformTests.java @@ -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); + } + +} 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 8e77878400..0c1d716603 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 @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.orm.jpa; +import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.SQLException; 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.context.annotation.AnnotationConfigApplicationContext; 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.entry; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; /** * Tests for {@link JpaProperties}. @@ -166,6 +171,40 @@ public class JpaPropertiesTests { .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") private DataSource mockStandaloneDataSource() throws SQLException { DataSource ds = mock(DataSource.class); @@ -173,6 +212,21 @@ public class JpaPropertiesTests { 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) { HibernateVersion.setRunning(hibernateVersion); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 091ae66d80..d6c36eeddf 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -1797,20 +1797,25 @@ annotation, e.g. === Configure JPA properties 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 -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"] ---- - spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.hibernate.naming.physical-strategy=com.example.MyPhysicalNamingStrategy - spring.jpa.database=H2 spring.jpa.show-sql=true ---- -The `ddl-auto` setting is a special case in that it has different defaults depending on -whether you are using an embedded database (`create-drop`) or not (`none`). In addition -all properties in `+spring.jpa.properties.*+` are passed through as normal JPA properties -(with the prefix stripped) when the local `EntityManagerFactory` is created. +In addition 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 that you are using. If you are using Hibernate 4, you can customize it using