From 24c83d12da26cf366bf893ccddc7bbc1e0115a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 22 Dec 2016 14:33:59 -0500 Subject: [PATCH] Autodetect spring.jpa.database from spring.datasource.url Previously, property `spring.jpa.database` should be provided. This commit allows to detect the database when `spring.datasource.url` is provided. Closes gh-7708 --- .../orm/jpa/DatabasePlatform.java | 74 ++++++++++++++++ .../orm/jpa/JpaBaseConfiguration.java | 12 ++- .../autoconfigure/orm/jpa/JpaProperties.java | 16 ++++ ...tomHibernateJpaAutoConfigurationTests.java | 46 +++++++++- .../orm/jpa/DatabasePlatformTests.java | 86 +++++++++++++++++++ .../orm/jpa/JpaPropertiesTests.java | 15 ++++ 6 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatform.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatformTests.java 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..f5e9727a99 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DatabasePlatform.java @@ -0,0 +1,74 @@ +/* + * 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 + * @version 1.5.0 + */ +public 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..912bb4dfea 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 @@ -41,6 +41,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -61,6 +63,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 +104,14 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { public JpaVendorAdapter jpaVendorAdapter() { AbstractJpaVendorAdapter adapter = createJpaVendorAdapter(); adapter.setShowSql(this.properties.isShowSql()); - adapter.setDatabase(this.properties.getDatabase()); + try { + String jdbcUrl = (String) JdbcUtils.extractDatabaseMetaData(this.dataSource, + "getURL"); + adapter.setDatabase(this.properties.determineDatabase(jdbcUrl)); + } + catch (MetaDataAccessException ex) { + throw new IllegalStateException("Unable to detect database type", ex); + } 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..68267e4978 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 @@ -24,6 +24,7 @@ import javax.sql.DataSource; 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.orm.jpa.vendor.Database; import org.springframework.util.StringUtils; @@ -33,6 +34,7 @@ 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") @@ -125,6 +127,20 @@ public class JpaProperties { return this.hibernate.getAdditionalProperties(this.properties, dataSource); } + /** + * Return the database form the jdbc url or the value for `spring.jpa.database`. + * @param jdbcUrl the url from `spring.datasource.url` + * @return {@code Database} + */ + public Database determineDatabase(String jdbcUrl) { + DatabasePlatform databasePlatform = DatabasePlatform.fromDatabaseDriver( + DatabaseDriver.fromJdbcUrl(jdbcUrl)); + if (databasePlatform != null) { + return databasePlatform.getDatabase(); + } + return this.database; + } + 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..0d6a6dc285 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 { @@ -57,7 +67,7 @@ public class CustomHibernateJpaAutoConfigurationTests { "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, + this.context.register(TestConfiguration.class, MockDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class, HibernateJpaAutoConfiguration.class); this.context.refresh(); @@ -101,10 +111,44 @@ public class CustomHibernateJpaAutoConfigurationTests { assertThat(hibernateProperties.get("hibernate.ejb.naming_strategy")).isNull(); } + @Test + public void testDefaultDatabaseForH2() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.driverClassName:org.h2.Driver", + "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..86898de9db 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 @@ -32,6 +32,7 @@ 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; @@ -166,6 +167,20 @@ public class JpaPropertiesTests { .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true"); } + @Test + public void determineH2DatabaseWhenJdbcUrlIsProvided() { + JpaProperties properties = load(HibernateVersion.V5); + Database database = properties.determineDatabase("jdbc:h2:mem:testdb"); + assertThat(database).isEqualTo(Database.H2); + } + + @Test + public void determineDefaultDatabaseWhenJdbcUrlIsProvided() { + JpaProperties properties = load(HibernateVersion.V5); + Database database = properties.determineDatabase("jdbc:unknown://localhost"); + assertThat(database).isEqualTo(Database.DEFAULT); + } + @SuppressWarnings("unchecked") private DataSource mockStandaloneDataSource() throws SQLException { DataSource ds = mock(DataSource.class);