From 1b4c5dffafdf4c8b52344d398998ec573339fb01 Mon Sep 17 00:00:00 2001 From: "Michael J. Simons" Date: Tue, 30 May 2017 11:32:03 +0200 Subject: [PATCH] Auto-detect jOOQ dialect See gh-9355 --- .../jooq/JooqAutoConfiguration.java | 11 +- .../autoconfigure/jooq/JooqProperties.java | 18 ++- .../autoconfigure/jooq/SQLDialectLookup.java | 84 ++++++++++++++ .../jooq/JooqAutoConfigurationTests.java | 3 +- .../jooq/SQLDialectLookupTests.java | 104 ++++++++++++++++++ .../main/asciidoc/spring-boot-features.adoc | 14 ++- 6 files changed, 224 insertions(+), 10 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SQLDialectLookup.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/SQLDialectLookupTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java index eb7208ae3bc..c5cc5d6170d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -48,6 +48,7 @@ import org.springframework.transaction.PlatformTransactionManager; * {@link EnableAutoConfiguration Auto-configuration} for JOOQ. * * @author Andreas Ahlenstorf + * @author Michael Simons * @since 1.3.0 */ @Configuration @@ -85,6 +86,8 @@ public class JooqAutoConfiguration { private final ConnectionProvider connection; + private final DataSource dataSource; + private final TransactionProvider transactionProvider; private final RecordMapperProvider recordMapperProvider; @@ -99,6 +102,7 @@ public class JooqAutoConfiguration { public DslContextConfiguration(JooqProperties properties, ConnectionProvider connectionProvider, + DataSource dataSource, ObjectProvider transactionProvider, ObjectProvider recordMapperProvider, ObjectProvider settings, @@ -107,6 +111,7 @@ public class JooqAutoConfiguration { ObjectProvider visitListenerProviders) { this.properties = properties; this.connection = connectionProvider; + this.dataSource = dataSource; this.transactionProvider = transactionProvider.getIfAvailable(); this.recordMapperProvider = recordMapperProvider.getIfAvailable(); this.settings = settings.getIfAvailable(); @@ -124,9 +129,7 @@ public class JooqAutoConfiguration { @ConditionalOnMissingBean(org.jooq.Configuration.class) public DefaultConfiguration jooqConfiguration() { DefaultConfiguration configuration = new DefaultConfiguration(); - if (this.properties.getSqlDialect() != null) { - configuration.set(this.properties.getSqlDialect()); - } + configuration.set(this.properties.determineSqlDialect(this.dataSource)); configuration.set(this.connection); if (this.transactionProvider != null) { configuration.set(this.transactionProvider); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java index 8c610c1062b..d15f64df82a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 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. @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.jooq; +import javax.sql.DataSource; + import org.jooq.SQLDialect; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -24,6 +26,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * Configuration properties for the JOOQ database library. * * @author Andreas Ahlenstorf + * @author Michael Simons * @since 1.3.0 */ @ConfigurationProperties(prefix = "spring.jooq") @@ -43,4 +46,17 @@ public class JooqProperties { this.sqlDialect = sqlDialect; } + /** + * Determine the {@link SQLDialect} to use based on this configuration and the primary + * {@link DataSource}. + * @param dataSource the auto-configured data source + * @return {@code SQLDialect} + */ + public SQLDialect determineSqlDialect(DataSource dataSource) { + if (this.sqlDialect != null) { + return this.sqlDialect; + } + return SQLDialectLookup.getDialect(dataSource); + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SQLDialectLookup.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SQLDialectLookup.java new file mode 100644 index 00000000000..39768a491a6 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SQLDialectLookup.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2017 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.jooq; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jooq.SQLDialect; + +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.jdbc.support.MetaDataAccessException; + +/** + * Utility to lookup well known {@link SQLDialect SQLDialects} from a {@link DataSource}. + * + * Note: This lookup only supports the SQL dialects that the open source edition of jOOQ supports. + * + * @author Michael Simons + */ +final class SQLDialectLookup { + + private static final Log logger = LogFactory.getLog(SQLDialectLookup.class); + + private static final Map LOOKUP; + + static { + Map map = new HashMap<>(); + map.put(DatabaseDriver.DERBY, SQLDialect.DERBY); + map.put(DatabaseDriver.H2, SQLDialect.H2); + map.put(DatabaseDriver.HSQLDB, SQLDialect.HSQLDB); + map.put(DatabaseDriver.MARIADB, SQLDialect.MARIADB); + map.put(DatabaseDriver.MYSQL, SQLDialect.MYSQL); + map.put(DatabaseDriver.POSTGRESQL, SQLDialect.POSTGRES); + map.put(DatabaseDriver.SQLITE, SQLDialect.SQLITE); + LOOKUP = Collections.unmodifiableMap(map); + } + + private SQLDialectLookup() { + } + + /** + * Return the most suitable {@link SQLDialect} for the given {@link DataSource}. + * @param dataSource the source {@link DataSource} + * @return the most suitable {@link SQLDialect} + */ + public static SQLDialect getDialect(DataSource dataSource) { + if (dataSource == null) { + return SQLDialect.DEFAULT; + } + try { + String url = (String) JdbcUtils.extractDatabaseMetaData(dataSource, "getURL"); + DatabaseDriver driver = DatabaseDriver.fromJdbcUrl(url); + SQLDialect sQLDialect = LOOKUP.get(driver); + if (sQLDialect != null) { + return sQLDialect; + } + } + catch (MetaDataAccessException ex) { + logger.warn("Unable to determine jdbc url from datasource", ex); + } + return SQLDialect.DEFAULT; + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java index cc345564b43..63da659345f 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java @@ -69,7 +69,6 @@ public class JooqAutoConfigurationTests { @Before public void init() { TestPropertyValues.of("spring.datasource.name:jooqtest").applyTo(this.context); - TestPropertyValues.of("spring.jooq.sql-dialect:H2").applyTo(this.context); } @After @@ -121,7 +120,7 @@ public class JooqAutoConfigurationTests { JooqAutoConfiguration.class); this.context.getBean(PlatformTransactionManager.class); DSLContext dsl = this.context.getBean(DSLContext.class); - assertThat(dsl.configuration().dialect()).isEqualTo(SQLDialect.H2); + assertThat(dsl.configuration().dialect()).isEqualTo(SQLDialect.HSQLDB); dsl.execute("create table jooqtest_tx (name varchar(255) primary key);"); dsl.transaction( new AssertFetch(dsl, "select count(*) as total from jooqtest_tx;", "0")); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/SQLDialectLookupTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/SQLDialectLookupTests.java new file mode 100644 index 00000000000..d0fda22170c --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/SQLDialectLookupTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2017 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.jooq; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; + +import javax.sql.DataSource; + +import org.jooq.SQLDialect; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SQLDialectLookup}. + * + * @author Michael Simons + */ +public class SQLDialectLookupTests { + + @Test + public void getDatabaseWhenDataSourceIsNullShouldReturnDefault() throws Exception { + assertThat(SQLDialectLookup.getDialect(null)).isEqualTo(SQLDialect.DEFAULT); + } + + @Test + public void getDatabaseWhenDataSourceIsUnknownShouldReturnDefault() throws Exception { + testGetDatabase("jdbc:idontexist:", SQLDialect.DEFAULT); + } + + @Test + public void getDatabaseWhenDerbyShouldReturnDerby() throws Exception { + testGetDatabase("jdbc:derby:", SQLDialect.DERBY); + } + + @Test + public void getDatabaseWhenH2ShouldReturnH2() throws Exception { + testGetDatabase("jdbc:h2:", SQLDialect.H2); + } + + @Test + public void getDatabaseWhenHsqldbShouldReturnHsqldb() throws Exception { + testGetDatabase("jdbc:hsqldb:", SQLDialect.HSQLDB); + } + + @Test + public void getDatabaseWhenMysqlShouldReturnMysql() throws Exception { + testGetDatabase("jdbc:mysql:", SQLDialect.MYSQL); + } + + @Test + public void getDatabaseWhenOracleShouldReturnOracle() throws Exception { + testGetDatabase("jdbc:oracle:", SQLDialect.DEFAULT); + } + + @Test + public void getDatabaseWhenPostgresShouldReturnPostgres() throws Exception { + testGetDatabase("jdbc:postgresql:", SQLDialect.POSTGRES); + } + + @Test + public void getDatabaseWhenSqlserverShouldReturnSqlserver() throws Exception { + testGetDatabase("jdbc:sqlserver:", SQLDialect.DEFAULT); + } + + @Test + public void getDatabaseWhenDb2ShouldReturnDb2() throws Exception { + testGetDatabase("jdbc:db2:", SQLDialect.DEFAULT); + } + + @Test + public void getDatabaseWhenInformixShouldReturnInformix() throws Exception { + testGetDatabase("jdbc:informix-sqli:", SQLDialect.DEFAULT); + } + + private void testGetDatabase(String url, SQLDialect expected) throws Exception { + DataSource dataSource = mock(DataSource.class); + Connection connection = mock(Connection.class); + DatabaseMetaData metaData = mock(DatabaseMetaData.class); + given(dataSource.getConnection()).willReturn(connection); + given(connection.getMetaData()).willReturn(metaData); + given(metaData.getURL()).willReturn(url); + SQLDialect sQLDialect = SQLDialectLookup.getDialect(dataSource); + assertThat(sQLDialect).isEqualTo(expected); + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 6b3b9580cf7..484dd64e0ad 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3214,14 +3214,22 @@ You can then use the `DSLContext` to construct your queries: ==== Customizing jOOQ -You can customize the SQL dialect used by jOOQ by setting `spring.jooq.sql-dialect` in -your `application.properties`. For example, to specify Postgres you would add: + +Spring Boot tries to determine the best SQL dialect for your datasource, but you can customize +the dialect used by jOOQ by setting `spring.jooq.sql-dialect` in your `application.properties`. +Spring Boot uses `SQLDialect.DEFAULT` if it cannot determine the type of your database. + +Spring Boot can only auto-configure dialects supported by the open source version of jOOQ. Dialects +supported by the commercial edition only have to be configured manually. For example, to specify +Oracle you would add: [source,properties,indent=0] ---- - spring.jooq.sql-dialect=Postgres + spring.jooq.sql-dialect=ORACLE12C ---- +Refer to `org.jooq.SQLDialect` for all valid dialects. + More advanced customizations can be achieved by defining your own `@Bean` definitions which will be used when the jOOQ `Configuration` is created. You can define beans for the following jOOQ Types: