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 eb7208ae3b..c5cc5d6170 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 8c610c1062..6241d9a0ed 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,14 +26,14 @@ 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") public class JooqProperties { /** - * SQLDialect JOOQ used when communicating with the configured datasource, for - * instance "POSTGRES". + * Sql dialect to use, auto-detected by default. */ private SQLDialect sqlDialect; @@ -43,4 +45,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 data source + * @return the {@code SQLDialect} to use for that {@link DataSource} + */ + 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 0000000000..300d3310dc --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SqlDialectLookup.java @@ -0,0 +1,82 @@ +/* + * 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}. + * + * @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 cc345564b4..823cdcf392 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 @@ -32,12 +32,10 @@ import org.jooq.TransactionalRunnable; import org.jooq.VisitListener; import org.jooq.VisitListenerProvider; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -46,6 +44,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.util.ObjectUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; @@ -56,6 +55,7 @@ import static org.junit.Assert.fail; * @author Andreas Ahlenstorf * @author Phillip Webb * @author Andy Wilkinson + * @author Stephane Nicoll */ public class JooqAutoConfigurationTests { @@ -64,13 +64,7 @@ public class JooqAutoConfigurationTests { @Rule public ExpectedException thrown = ExpectedException.none(); - private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - - @Before - public void init() { - TestPropertyValues.of("spring.datasource.name:jooqtest").applyTo(this.context); - TestPropertyValues.of("spring.jooq.sql-dialect:H2").applyTo(this.context); - } + private AnnotationConfigApplicationContext context; @After public void close() { @@ -81,16 +75,14 @@ public class JooqAutoConfigurationTests { @Test public void noDataSource() throws Exception { - registerAndRefresh(JooqAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); + load(); assertThat(this.context.getBeanNamesForType(DSLContext.class).length) .isEqualTo(0); } @Test public void jooqWithoutTx() throws Exception { - registerAndRefresh(JooqDataSourceConfiguration.class, JooqAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); + load(JooqDataSourceConfiguration.class); assertThat(getBeanNames(PlatformTransactionManager.class)).isEqualTo(NO_BEANS); assertThat(getBeanNames(SpringTransactionProvider.class)).isEqualTo(NO_BEANS); DSLContext dsl = this.context.getBean(DSLContext.class); @@ -116,12 +108,10 @@ public class JooqAutoConfigurationTests { @Test public void jooqWithTx() throws Exception { - registerAndRefresh(JooqDataSourceConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, TxManagerConfiguration.class, - JooqAutoConfiguration.class); + load(JooqDataSourceConfiguration.class, TxManagerConfiguration.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")); @@ -144,11 +134,9 @@ public class JooqAutoConfigurationTests { @Test public void customProvidersArePickedUp() { - registerAndRefresh(JooqDataSourceConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, TxManagerConfiguration.class, + load(JooqDataSourceConfiguration.class, TxManagerConfiguration.class, TestRecordMapperProvider.class, TestRecordListenerProvider.class, - TestExecuteListenerProvider.class, TestVisitListenerProvider.class, - JooqAutoConfiguration.class); + TestExecuteListenerProvider.class, TestVisitListenerProvider.class); DSLContext dsl = this.context.getBean(DSLContext.class); assertThat(dsl.configuration().recordMapperProvider().getClass()) .isEqualTo(TestRecordMapperProvider.class); @@ -159,17 +147,25 @@ public class JooqAutoConfigurationTests { @Test public void relaxedBindingOfSqlDialect() { - TestPropertyValues.of( - "spring.jooq.sql-dialect:PoSTGrES").applyTo(this.context); - registerAndRefresh(JooqDataSourceConfiguration.class, - JooqAutoConfiguration.class); + load(new Class[] { JooqDataSourceConfiguration.class }, "spring.jooq.sql-dialect:PoSTGrES"); assertThat(this.context.getBean(org.jooq.Configuration.class).dialect()) .isEqualTo(SQLDialect.POSTGRES); } - private void registerAndRefresh(Class... annotatedClasses) { - this.context.register(annotatedClasses); - this.context.refresh(); + private void load(Class... configs) { + load(configs, new String[0]); + } + + private void load(Class[] configs, String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + TestPropertyValues.of("spring.datasource.name:jooqtest").applyTo(ctx); + TestPropertyValues.of(environment).applyTo(ctx); + if (!ObjectUtils.isEmpty(configs)) { + ctx.register(configs); + } + ctx.register(JooqAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; } private String[] getBeanNames(Class type) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqPropertiesTest.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqPropertiesTest.java new file mode 100644 index 0000000000..362885137f --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqPropertiesTest.java @@ -0,0 +1,125 @@ +/* + * 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 java.sql.SQLException; + +import javax.sql.DataSource; + +import org.jooq.SQLDialect; +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +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 JooqProperties}. + * + * @author Stephane Nicoll + */ +public class JooqPropertiesTest { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void determineSqlDialectNoCheckIfDialectIsSet() throws SQLException { + JooqProperties properties = load("spring.jooq.sql-dialect=postgres"); + DataSource dataSource = mockStandaloneDataSource(); + SQLDialect sqlDialect = properties.determineSqlDialect(dataSource); + assertThat(sqlDialect).isEqualTo(SQLDialect.POSTGRES); + verify(dataSource, never()).getConnection(); + } + + @Test + public void determineSqlDialectWithKnownUrl() { + JooqProperties properties = load(); + SQLDialect sqlDialect = properties + .determineSqlDialect(mockDataSource("jdbc:h2:mem:testdb")); + assertThat(sqlDialect).isEqualTo(SQLDialect.H2); + } + + @Test + public void determineSqlDialectWithKnownUrlAndUserConfig() { + JooqProperties properties = load("spring.jooq.sql-dialect=mysql"); + SQLDialect sqlDialect = properties + .determineSqlDialect(mockDataSource("jdbc:h2:mem:testdb")); + assertThat(sqlDialect).isEqualTo(SQLDialect.MYSQL); + } + + @Test + public void determineSqlDialectWithUnknownUrl() { + JooqProperties properties = load(); + SQLDialect sqlDialect = properties + .determineSqlDialect(mockDataSource("jdbc:unknown://localhost")); + assertThat(sqlDialect).isEqualTo(SQLDialect.DEFAULT); + } + + private DataSource mockStandaloneDataSource() throws SQLException { + DataSource ds = mock(DataSource.class); + given(ds.getConnection()).willThrow(SQLException.class); + 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 JooqProperties load(String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + TestPropertyValues.of(environment).applyTo(ctx); + ctx.register(TestConfiguration.class); + ctx.refresh(); + this.context = ctx; + return this.context.getBean(JooqProperties.class); + } + + @Configuration + @EnableConfigurationProperties(JooqProperties.class) + static class TestConfiguration { + + } + +} 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 0000000000..dd3f4e7aa8 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/SqlDialectLookupTests.java @@ -0,0 +1,105 @@ +/* + * 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 + * @author Stephane Nicoll + */ +public class SqlDialectLookupTests { + + @Test + public void getSqlDialectWhenDataSourceIsNullShouldReturnDefault() throws Exception { + assertThat(SqlDialectLookup.getDialect(null)).isEqualTo(SQLDialect.DEFAULT); + } + + @Test + public void getSqlDialectWhenDataSourceIsUnknownShouldReturnDefault() throws Exception { + testGetSqlDialect("jdbc:idontexist:", SQLDialect.DEFAULT); + } + + @Test + public void getSqlDialectWhenDerbyShouldReturnDerby() throws Exception { + testGetSqlDialect("jdbc:derby:", SQLDialect.DERBY); + } + + @Test + public void getSqlDialectWhenH2ShouldReturnH2() throws Exception { + testGetSqlDialect("jdbc:h2:", SQLDialect.H2); + } + + @Test + public void getSqlDialectWhenHsqldbShouldReturnHsqldb() throws Exception { + testGetSqlDialect("jdbc:hsqldb:", SQLDialect.HSQLDB); + } + + @Test + public void getSqlDialectWhenMysqlShouldReturnMysql() throws Exception { + testGetSqlDialect("jdbc:mysql:", SQLDialect.MYSQL); + } + + @Test + public void getSqlDialectWhenOracleShouldReturnOracle() throws Exception { + testGetSqlDialect("jdbc:oracle:", SQLDialect.DEFAULT); + } + + @Test + public void getSqlDialectWhenPostgresShouldReturnPostgres() throws Exception { + testGetSqlDialect("jdbc:postgresql:", SQLDialect.POSTGRES); + } + + @Test + public void getSqlDialectWhenSqlserverShouldReturnSqlserver() throws Exception { + testGetSqlDialect("jdbc:sqlserver:", SQLDialect.DEFAULT); + } + + @Test + public void getSqlDialectWhenDb2ShouldReturnDb2() throws Exception { + testGetSqlDialect("jdbc:db2:", SQLDialect.DEFAULT); + } + + @Test + public void getSqlDialectWhenInformixShouldReturnInformix() throws Exception { + testGetSqlDialect("jdbc:informix-sqli:", SQLDialect.DEFAULT); + } + + private void testGetSqlDialect(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/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 67367e74c6..a3643a269f 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -708,7 +708,7 @@ content into your application; rather pick only the properties that you need. spring.h2.console.settings.web-allow-others=false # Enable remote access. # JOOQ ({sc-spring-boot-autoconfigure}/jooq/JooqAutoConfiguration.{sc-ext}[JooqAutoConfiguration]) - spring.jooq.sql-dialect= # SQLDialect JOOQ used when communicating with the configured datasource. For instance `POSTGRES` + spring.jooq.sql-dialect= # Sql dialect to use, auto-detected by default. # JPA ({sc-spring-boot-autoconfigure}/orm/jpa/JpaBaseConfiguration.{sc-ext}[JpaBaseConfiguration], {sc-spring-boot-autoconfigure}/orm/jpa/HibernateJpaAutoConfiguration.{sc-ext}[HibernateJpaAutoConfiguration]) spring.data.jpa.repositories.enabled=true # Enable JPA repositories. 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 6b3b9580cf..463bda3d6f 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3213,15 +3213,17 @@ 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: +==== jOOQ SQL dialect +Spring Boot determines the SQL dialect to use for your datasource unless the +`spring.jooq.sql-dialect` property has been configured. If the dialect couldn't be +detected, `DEFAULT` is used. -[source,properties,indent=0] ----- - spring.jooq.sql-dialect=Postgres ----- +NOTE: Spring Boot can only auto-configure dialects supported by the open source version of +jOOQ. + + +==== Customizing jOOQ 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: