Reinstate support for jOOQ as it now supports Jakarta EE 9

Closes gh-29271
pull/29274/head
Andy Wilkinson 3 years ago
parent ccc924b908
commit c2e86096cd

@ -98,6 +98,9 @@ dependencies {
}
optional("org.hibernate.validator:hibernate-validator")
optional("org.influxdb:influxdb-java")
optional("org.jooq:jooq") {
exclude group: "javax.xml.bind", module: "jaxb-api"
}
optional("org.liquibase:liquibase-core") {
exclude group: "javax.xml.bind", module: "jaxb-api"
}

@ -0,0 +1,37 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.jooq.impl.DefaultConfiguration;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link DefaultConfiguration} whilst retaining default auto-configuration.
*
* @author Stephane Nicoll
* @since 2.5.0
*/
@FunctionalInterface
public interface DefaultConfigurationCustomizer {
/**
* Customize the {@link DefaultConfiguration jOOQ Configuration}.
* @param configuration the configuration to customize
*/
void customize(DefaultConfiguration configuration);
}

@ -0,0 +1,57 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.jooq.DSLContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector;
/**
* {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all
* {@link DSLContext} beans should "depend on" one or more specific beans.
*
* @author Eddú Meléndez
* @since 2.3.9
* @see BeanDefinition#setDependsOn(String[])
* @deprecated since 2.5.0 for removal in 2.7.0 in favor of
* {@link DependsOnDatabaseInitializationDetector}
*/
@Deprecated
public class DslContextDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor {
/**
* Creates a new {@code DslContextDependsOnPostProcessor} that will set up
* dependencies upon beans with the given names.
* @param dependsOn names of the beans to depend upon
*/
public DslContextDependsOnPostProcessor(String... dependsOn) {
super(DSLContext.class, dependsOn);
}
/**
* Creates a new {@code DslContextDependsOnPostProcessor} that will set up
* dependencies upon beans with the given types.
* @param dependsOn types of the beans to depend upon
*/
public DslContextDependsOnPostProcessor(Class<?>... dependsOn) {
super(DSLContext.class, dependsOn);
}
}

@ -0,0 +1,154 @@
/*
* Copyright 2012-2022 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
*
* https://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 javax.sql.DataSource;
import org.jooq.ConnectionProvider;
import org.jooq.DSLContext;
import org.jooq.ExecuteListenerProvider;
import org.jooq.ExecutorProvider;
import org.jooq.RecordListenerProvider;
import org.jooq.RecordMapperProvider;
import org.jooq.RecordUnmapperProvider;
import org.jooq.TransactionListenerProvider;
import org.jooq.TransactionProvider;
import org.jooq.VisitListenerProvider;
import org.jooq.conf.Settings;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultDSLContext;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.transaction.PlatformTransactionManager;
/**
* {@link EnableAutoConfiguration Auto-configuration} for JOOQ.
*
* @author Andreas Ahlenstorf
* @author Michael Simons
* @author Dmytro Nosan
* @since 1.3.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DSLContext.class)
@ConditionalOnBean(DataSource.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class })
public class JooqAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ConnectionProvider.class)
public DataSourceConnectionProvider dataSourceConnectionProvider(DataSource dataSource) {
return new DataSourceConnectionProvider(new TransactionAwareDataSourceProxy(dataSource));
}
@Bean
@ConditionalOnBean(PlatformTransactionManager.class)
public SpringTransactionProvider transactionProvider(PlatformTransactionManager txManager) {
return new SpringTransactionProvider(txManager);
}
@Bean
@Order(0)
public DefaultExecuteListenerProvider jooqExceptionTranslatorExecuteListenerProvider() {
return new DefaultExecuteListenerProvider(new JooqExceptionTranslator());
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DSLContext.class)
@EnableConfigurationProperties(JooqProperties.class)
public static class DslContextConfiguration {
@Bean
public DefaultDSLContext dslContext(org.jooq.Configuration configuration) {
return new DefaultDSLContext(configuration);
}
@Bean
@ConditionalOnMissingBean(org.jooq.Configuration.class)
public DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider,
DataSource dataSource, ObjectProvider<ExecuteListenerProvider> executeListenerProviders,
ObjectProvider<DefaultConfigurationCustomizer> configurationCustomizers) {
DefaultConfiguration configuration = new DefaultConfiguration();
configuration.set(properties.determineSqlDialect(dataSource));
configuration.set(connectionProvider);
configuration.set(executeListenerProviders.orderedStream().toArray(ExecuteListenerProvider[]::new));
configurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
return configuration;
}
@Bean
@Deprecated
public DefaultConfigurationCustomizer jooqProvidersDefaultConfigurationCustomizer(
ObjectProvider<TransactionProvider> transactionProvider,
ObjectProvider<RecordMapperProvider> recordMapperProvider,
ObjectProvider<RecordUnmapperProvider> recordUnmapperProvider, ObjectProvider<Settings> settings,
ObjectProvider<RecordListenerProvider> recordListenerProviders,
ObjectProvider<VisitListenerProvider> visitListenerProviders,
ObjectProvider<TransactionListenerProvider> transactionListenerProviders,
ObjectProvider<ExecutorProvider> executorProvider) {
return new OrderedDefaultConfigurationCustomizer((configuration) -> {
transactionProvider.ifAvailable(configuration::set);
recordMapperProvider.ifAvailable(configuration::set);
recordUnmapperProvider.ifAvailable(configuration::set);
settings.ifAvailable(configuration::set);
executorProvider.ifAvailable(configuration::set);
configuration.set(recordListenerProviders.orderedStream().toArray(RecordListenerProvider[]::new));
configuration.set(visitListenerProviders.orderedStream().toArray(VisitListenerProvider[]::new));
configuration.setTransactionListenerProvider(
transactionListenerProviders.orderedStream().toArray(TransactionListenerProvider[]::new));
});
}
}
private static class OrderedDefaultConfigurationCustomizer implements DefaultConfigurationCustomizer, Ordered {
private final DefaultConfigurationCustomizer delegate;
OrderedDefaultConfigurationCustomizer(DefaultConfigurationCustomizer delegate) {
this.delegate = delegate;
}
@Override
public void customize(DefaultConfiguration configuration) {
this.delegate.customize(configuration);
}
@Override
public int getOrder() {
return 0;
}
}
}

@ -0,0 +1,97 @@
/*
* Copyright 2012-2022 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
*
* https://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.SQLException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jooq.ExecuteContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DefaultExecuteListener;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
/**
* Transforms {@link java.sql.SQLException} into a Spring-specific
* {@link DataAccessException}.
*
* @author Lukas Eder
* @author Andreas Ahlenstorf
* @author Phillip Webb
* @author Stephane Nicoll
* @since 1.5.10
*/
public class JooqExceptionTranslator extends DefaultExecuteListener {
// Based on the jOOQ-spring-example from https://github.com/jOOQ/jOOQ
private static final Log logger = LogFactory.getLog(JooqExceptionTranslator.class);
@Override
public void exception(ExecuteContext context) {
SQLExceptionTranslator translator = getTranslator(context);
// The exception() callback is not only triggered for SQL exceptions but also for
// "normal" exceptions. In those cases sqlException() returns null.
SQLException exception = context.sqlException();
while (exception != null) {
handle(context, translator, exception);
exception = exception.getNextException();
}
}
private SQLExceptionTranslator getTranslator(ExecuteContext context) {
SQLDialect dialect = context.configuration().dialect();
if (dialect != null && dialect.thirdParty() != null) {
String dbName = dialect.thirdParty().springDbName();
if (dbName != null) {
return new SQLErrorCodeSQLExceptionTranslator(dbName);
}
}
return new SQLStateSQLExceptionTranslator();
}
/**
* Handle a single exception in the chain. SQLExceptions might be nested multiple
* levels deep. The outermost exception is usually the least interesting one ("Call
* getNextException to see the cause."). Therefore the innermost exception is
* propagated and all other exceptions are logged.
* @param context the execute context
* @param translator the exception translator
* @param exception the exception
*/
private void handle(ExecuteContext context, SQLExceptionTranslator translator, SQLException exception) {
DataAccessException translated = translate(context, translator, exception);
if (exception.getNextException() == null) {
if (translated != null) {
context.exception(translated);
}
}
else {
logger.error("Execution of SQL statement failed.", (translated != null) ? translated : exception);
}
}
private DataAccessException translate(ExecuteContext context, SQLExceptionTranslator translator,
SQLException exception) {
return translator.translate("jOOQ", context.sql(), exception);
}
}

@ -0,0 +1,61 @@
/*
* Copyright 2012-2022 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
*
* https://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 javax.sql.DataSource;
import org.jooq.SQLDialect;
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 {
/**
* SQL dialect to use. Auto-detected by default.
*/
private SQLDialect sqlDialect;
public SQLDialect getSqlDialect() {
return this.sqlDialect;
}
public void setSqlDialect(SQLDialect sqlDialect) {
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);
}
}

@ -0,0 +1,69 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.jooq.DSLContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.core.Ordered;
class NoDslContextBeanFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchBeanDefinitionException>
implements Ordered, BeanFactoryAware {
private BeanFactory beanFactory;
@Override
protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause) {
if (DSLContext.class.equals(cause.getBeanType()) && hasR2dbcAutoConfiguration()) {
return new FailureAnalysis(
"jOOQ has not been auto-configured as R2DBC has been auto-configured in favor of JDBC and jOOQ "
+ "auto-configuration does not yet support R2DBC. ",
"To use jOOQ with JDBC, exclude R2dbcAutoConfiguration. To use jOOQ with R2DBC, define your own "
+ "jOOQ configuration.",
cause);
}
return null;
}
private boolean hasR2dbcAutoConfiguration() {
try {
this.beanFactory.getBean(R2dbcAutoConfiguration.class);
return true;
}
catch (Exception ex) {
return false;
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}

@ -0,0 +1,44 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.jooq.Transaction;
import org.springframework.transaction.TransactionStatus;
/**
* Adapts a Spring transaction for JOOQ.
*
* @author Lukas Eder
* @author Andreas Ahlenstorf
* @author Phillip Webb
*/
class SpringTransaction implements Transaction {
// Based on the jOOQ-spring-example from https://github.com/jOOQ/jOOQ
private final TransactionStatus transactionStatus;
SpringTransaction(TransactionStatus transactionStatus) {
this.transactionStatus = transactionStatus;
}
TransactionStatus getTxStatus() {
return this.transactionStatus;
}
}

@ -0,0 +1,67 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.jooq.TransactionContext;
import org.jooq.TransactionProvider;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* Allows Spring Transaction to be used with JOOQ.
*
* @author Lukas Eder
* @author Andreas Ahlenstorf
* @author Phillip Webb
* @since 1.5.10
*/
public class SpringTransactionProvider implements TransactionProvider {
// Based on the jOOQ-spring-example from https://github.com/jOOQ/jOOQ
private final PlatformTransactionManager transactionManager;
public SpringTransactionProvider(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public void begin(TransactionContext context) {
TransactionDefinition definition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_NESTED);
TransactionStatus status = this.transactionManager.getTransaction(definition);
context.transaction(new SpringTransaction(status));
}
@Override
public void commit(TransactionContext ctx) {
this.transactionManager.commit(getTransactionStatus(ctx));
}
@Override
public void rollback(TransactionContext ctx) {
this.transactionManager.rollback(getTransactionStatus(ctx));
}
private TransactionStatus getTransactionStatus(TransactionContext ctx) {
SpringTransaction transaction = (SpringTransaction) ctx.transaction();
return transaction.getTxStatus();
}
}

@ -0,0 +1,66 @@
/*
* Copyright 2012-2022 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
*
* https://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.DatabaseMetaData;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jooq.SQLDialect;
import org.jooq.tools.jdbc.JDBCUtils;
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
* @author Lukas Eder
*/
final class SqlDialectLookup {
private static final Log logger = LogFactory.getLog(SqlDialectLookup.class);
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}
*/
static SQLDialect getDialect(DataSource dataSource) {
if (dataSource == null) {
return SQLDialect.DEFAULT;
}
try {
String url = JdbcUtils.extractDatabaseMetaData(dataSource, DatabaseMetaData::getURL);
SQLDialect sqlDialect = JDBCUtils.dialect(url);
if (sqlDialect != null) {
return sqlDialect;
}
}
catch (MetaDataAccessException ex) {
logger.warn("Unable to determine jdbc url from datasource", ex);
}
return SQLDialect.DEFAULT;
}
}

@ -0,0 +1,20 @@
/*
* Copyright 2012-2022 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
*
* https://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.
*/
/**
* Auto-configuration for JOOQ.
*/
package org.springframework.boot.autoconfigure.jooq;

@ -87,6 +87,7 @@ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConf
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
@ -158,6 +159,7 @@ org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinition
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalzyer,\

@ -36,11 +36,15 @@ import org.flywaydb.core.internal.license.FlywayTeamsUpgradeRequiredException;
import org.flywaydb.core.internal.plugin.PluginRegister;
import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension;
import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DefaultDSLContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
@ -649,6 +653,34 @@ class FlywayAutoConfigurationTests {
.run(validateFlywayTeamsPropertyOnly("skipExecutingMigrations"));
}
@Test
void whenFlywayIsAutoConfiguredThenJooqDslContextDependsOnFlywayBeans() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class)
.run((context) -> {
BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext");
assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayInitializer", "flyway");
});
}
@Test
void whenCustomMigrationInitializerIsDefinedThenJooqDslContextDependsOnIt() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class,
CustomFlywayMigrationInitializer.class).run((context) -> {
BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext");
assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayMigrationInitializer",
"flyway");
});
}
@Test
void whenCustomFlywayIsDefinedThenJooqDslContextDependsOnIt() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class,
CustomFlyway.class).run((context) -> {
BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext");
assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("customFlyway");
});
}
@Test
void baselineMigrationPrefixIsCorrectlyMapped() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
@ -920,6 +952,16 @@ class FlywayAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class JooqConfiguration {
@Bean
DSLContext dslContext() {
return new DefaultDSLContext(SQLDialect.H2);
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(DataSourceProperties.class)
abstract static class AbstractUserH2DataSourceConfiguration {

@ -0,0 +1,262 @@
/*
* Copyright 2012-2022 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
*
* https://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 javax.sql.DataSource;
import org.jooq.CharsetProvider;
import org.jooq.ConnectionProvider;
import org.jooq.ConverterProvider;
import org.jooq.DSLContext;
import org.jooq.ExecuteListener;
import org.jooq.ExecuteListenerProvider;
import org.jooq.ExecutorProvider;
import org.jooq.RecordListenerProvider;
import org.jooq.RecordMapperProvider;
import org.jooq.RecordUnmapperProvider;
import org.jooq.SQLDialect;
import org.jooq.TransactionListenerProvider;
import org.jooq.TransactionalRunnable;
import org.jooq.VisitListenerProvider;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JooqAutoConfiguration}.
*
* @author Andreas Ahlenstorf
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Dmytro Nosan
*/
class JooqAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JooqAutoConfiguration.class))
.withPropertyValues("spring.datasource.name:jooqtest");
@Test
void noDataSource() {
this.contextRunner.run((context) -> assertThat(context.getBeansOfType(DSLContext.class)).isEmpty());
}
@Test
void jooqWithoutTx() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class).run((context) -> {
assertThat(context).doesNotHaveBean(PlatformTransactionManager.class);
assertThat(context).doesNotHaveBean(SpringTransactionProvider.class);
DSLContext dsl = context.getBean(DSLContext.class);
dsl.execute("create table jooqtest (name varchar(255) primary key);");
dsl.transaction(new AssertFetch(dsl, "select count(*) as total from jooqtest;", "0"));
dsl.transaction(new ExecuteSql(dsl, "insert into jooqtest (name) values ('foo');"));
dsl.transaction(new AssertFetch(dsl, "select count(*) as total from jooqtest;", "1"));
assertThatExceptionOfType(DataIntegrityViolationException.class)
.isThrownBy(() -> dsl.transaction(new ExecuteSql(dsl, "insert into jooqtest (name) values ('bar');",
"insert into jooqtest (name) values ('foo');")));
dsl.transaction(new AssertFetch(dsl, "select count(*) as total from jooqtest;", "2"));
});
}
@Test
void jooqWithTx() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class, TxManagerConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(PlatformTransactionManager.class);
DSLContext dsl = context.getBean(DSLContext.class);
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"));
dsl.transaction(new ExecuteSql(dsl, "insert into jooqtest_tx (name) values ('foo');"));
dsl.transaction(new AssertFetch(dsl, "select count(*) as total from jooqtest_tx;", "1"));
assertThatExceptionOfType(DataIntegrityViolationException.class).isThrownBy(
() -> dsl.transaction(new ExecuteSql(dsl, "insert into jooqtest (name) values ('bar');",
"insert into jooqtest (name) values ('foo');")));
dsl.transaction(new AssertFetch(dsl, "select count(*) as total from jooqtest_tx;", "1"));
});
}
@Test
void jooqWithDefaultConnectionProvider() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class).run((context) -> {
DSLContext dsl = context.getBean(DSLContext.class);
ConnectionProvider connectionProvider = dsl.configuration().connectionProvider();
assertThat(connectionProvider).isInstanceOf(DataSourceConnectionProvider.class);
DataSource connectionProviderDataSource = ((DataSourceConnectionProvider) connectionProvider).dataSource();
assertThat(connectionProviderDataSource).isInstanceOf(TransactionAwareDataSourceProxy.class);
});
}
@Test
void jooqWithDefaultExecuteListenerProvider() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class).run((context) -> {
DSLContext dsl = context.getBean(DSLContext.class);
assertThat(dsl.configuration().executeListenerProviders()).hasSize(1);
});
}
@Test
void jooqWithSeveralExecuteListenerProviders() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class, TestExecuteListenerProvider.class)
.run((context) -> {
DSLContext dsl = context.getBean(DSLContext.class);
ExecuteListenerProvider[] executeListenerProviders = dsl.configuration().executeListenerProviders();
assertThat(executeListenerProviders).hasSize(2);
assertThat(executeListenerProviders[0]).isInstanceOf(DefaultExecuteListenerProvider.class);
assertThat(executeListenerProviders[1]).isInstanceOf(TestExecuteListenerProvider.class);
});
}
@Test
void dslContextWithConfigurationCustomizersAreApplied() {
ConverterProvider converterProvider = mock(ConverterProvider.class);
CharsetProvider charsetProvider = mock(CharsetProvider.class);
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
.withBean("configurationCustomizer1", DefaultConfigurationCustomizer.class,
() -> (configuration) -> configuration.set(converterProvider))
.withBean("configurationCustomizer2", DefaultConfigurationCustomizer.class,
() -> (configuration) -> configuration.set(charsetProvider))
.run((context) -> {
DSLContext dsl = context.getBean(DSLContext.class);
assertThat(dsl.configuration().converterProvider()).isSameAs(converterProvider);
assertThat(dsl.configuration().charsetProvider()).isSameAs(charsetProvider);
});
}
@Test
@Deprecated
void customProvidersArePickedUp() {
RecordMapperProvider recordMapperProvider = mock(RecordMapperProvider.class);
RecordUnmapperProvider recordUnmapperProvider = mock(RecordUnmapperProvider.class);
RecordListenerProvider recordListenerProvider = mock(RecordListenerProvider.class);
VisitListenerProvider visitListenerProvider = mock(VisitListenerProvider.class);
TransactionListenerProvider transactionListenerProvider = mock(TransactionListenerProvider.class);
ExecutorProvider executorProvider = mock(ExecutorProvider.class);
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class, TxManagerConfiguration.class)
.withBean(RecordMapperProvider.class, () -> recordMapperProvider)
.withBean(RecordUnmapperProvider.class, () -> recordUnmapperProvider)
.withBean(RecordListenerProvider.class, () -> recordListenerProvider)
.withBean(VisitListenerProvider.class, () -> visitListenerProvider)
.withBean(TransactionListenerProvider.class, () -> transactionListenerProvider)
.withBean(ExecutorProvider.class, () -> executorProvider).run((context) -> {
DSLContext dsl = context.getBean(DSLContext.class);
assertThat(dsl.configuration().recordMapperProvider()).isSameAs(recordMapperProvider);
assertThat(dsl.configuration().recordUnmapperProvider()).isSameAs(recordUnmapperProvider);
assertThat(dsl.configuration().executorProvider()).isSameAs(executorProvider);
assertThat(dsl.configuration().recordListenerProviders()).containsExactly(recordListenerProvider);
assertThat(dsl.configuration().visitListenerProviders()).containsExactly(visitListenerProvider);
assertThat(dsl.configuration().transactionListenerProviders())
.containsExactly(transactionListenerProvider);
});
}
@Test
void relaxedBindingOfSqlDialect() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
.withPropertyValues("spring.jooq.sql-dialect:PoSTGrES")
.run((context) -> assertThat(context.getBean(org.jooq.Configuration.class).dialect())
.isEqualTo(SQLDialect.POSTGRES));
}
static class AssertFetch implements TransactionalRunnable {
private final DSLContext dsl;
private final String sql;
private final String expected;
AssertFetch(DSLContext dsl, String sql, String expected) {
this.dsl = dsl;
this.sql = sql;
this.expected = expected;
}
@Override
public void run(org.jooq.Configuration configuration) {
assertThat(this.dsl.fetch(this.sql).getValue(0, 0).toString()).isEqualTo(this.expected);
}
}
static class ExecuteSql implements TransactionalRunnable {
private final DSLContext dsl;
private final String[] sql;
ExecuteSql(DSLContext dsl, String... sql) {
this.dsl = dsl;
this.sql = sql;
}
@Override
public void run(org.jooq.Configuration configuration) {
for (String statement : this.sql) {
this.dsl.execute(statement);
}
}
}
@Configuration(proxyBeanMethods = false)
static class JooqDataSourceConfiguration {
@Bean
DataSource jooqDataSource() {
return DataSourceBuilder.create().url("jdbc:hsqldb:mem:jooqtest").username("sa").build();
}
}
@Configuration(proxyBeanMethods = false)
static class TxManagerConfiguration {
@Bean
PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@Order(100)
static class TestExecuteListenerProvider implements ExecuteListenerProvider {
@Override
public ExecuteListener provide() {
return null;
}
}
}

@ -0,0 +1,90 @@
/*
* Copyright 2012-2022 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
*
* https://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.SQLException;
import org.jooq.Configuration;
import org.jooq.ExecuteContext;
import org.jooq.SQLDialect;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.springframework.jdbc.BadSqlGrammarException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link JooqExceptionTranslator}
*
* @author Andy Wilkinson
*/
class JooqExceptionTranslatorTests {
private final JooqExceptionTranslator exceptionTranslator = new JooqExceptionTranslator();
@ParameterizedTest(name = "{0}")
@MethodSource
void exceptionTranslation(SQLDialect dialect, SQLException sqlException) {
ExecuteContext context = mock(ExecuteContext.class);
Configuration configuration = mock(Configuration.class);
given(context.configuration()).willReturn(configuration);
given(configuration.dialect()).willReturn(dialect);
given(context.sqlException()).willReturn(sqlException);
this.exceptionTranslator.exception(context);
ArgumentCaptor<RuntimeException> captor = ArgumentCaptor.forClass(RuntimeException.class);
verify(context).exception(captor.capture());
assertThat(captor.getValue()).isInstanceOf(BadSqlGrammarException.class);
}
@Test
void whenExceptionCannotBeTranslatedThenExecuteContextExceptionIsNotCalled() {
ExecuteContext context = mock(ExecuteContext.class);
Configuration configuration = mock(Configuration.class);
given(context.configuration()).willReturn(configuration);
given(configuration.dialect()).willReturn(SQLDialect.POSTGRES);
given(context.sqlException()).willReturn(new SQLException(null, null, 123456789));
this.exceptionTranslator.exception(context);
verify(context, times(0)).exception(any());
}
static Object[] exceptionTranslation() {
return new Object[] { new Object[] { SQLDialect.DERBY, sqlException("42802") },
new Object[] { SQLDialect.H2, sqlException(42000) },
new Object[] { SQLDialect.HSQLDB, sqlException(-22) },
new Object[] { SQLDialect.MARIADB, sqlException(1054) },
new Object[] { SQLDialect.MYSQL, sqlException(1054) },
new Object[] { SQLDialect.POSTGRES, sqlException("03000") },
new Object[] { SQLDialect.SQLITE, sqlException("21000") } };
}
private static SQLException sqlException(String sqlState) {
return new SQLException(null, sqlState);
}
private static SQLException sqlException(int vendorCode) {
return new SQLException(null, null, vendorCode);
}
}

@ -0,0 +1,122 @@
/*
* Copyright 2012-2022 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
*
* https://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.jupiter.api.AfterEach;
import org.junit.jupiter.api.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
*/
class JooqPropertiesTests {
private AnnotationConfigApplicationContext context;
@AfterEach
void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
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
void determineSqlDialectWithKnownUrl() {
JooqProperties properties = load();
SQLDialect sqlDialect = properties.determineSqlDialect(mockDataSource("jdbc:h2:mem:testdb"));
assertThat(sqlDialect).isEqualTo(SQLDialect.H2);
}
@Test
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
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 ex) {
// 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(proxyBeanMethods = false)
@EnableConfigurationProperties(JooqProperties.class)
static class TestConfiguration {
}
}

@ -0,0 +1,56 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.jooq.DSLContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link NoDslContextBeanFailureAnalyzer}.
*
* @author Andy Wilkinson
*/
class NoDslContextBeanFailureAnalyzerTests {
private final NoDslContextBeanFailureAnalyzer failureAnalyzer = new NoDslContextBeanFailureAnalyzer();
@Test
void noAnalysisWithoutR2dbcAutoConfiguration() {
new ApplicationContextRunner().run((context) -> {
this.failureAnalyzer.setBeanFactory(context.getBeanFactory());
assertThat(this.failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class))).isNull();
});
}
@Test
void analysisWithR2dbcAutoConfiguration() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
.run((context) -> {
this.failureAnalyzer.setBeanFactory(context.getBeanFactory());
assertThat(this.failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class)))
.isNotNull();
});
}
}

@ -0,0 +1,105 @@
/*
* Copyright 2012-2022 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
*
* https://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.jupiter.api.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
*/
class SqlDialectLookupTests {
@Test
void getSqlDialectWhenDataSourceIsNullShouldReturnDefault() {
assertThat(SqlDialectLookup.getDialect(null)).isEqualTo(SQLDialect.DEFAULT);
}
@Test
void getSqlDialectWhenDataSourceIsUnknownShouldReturnDefault() throws Exception {
testGetSqlDialect("jdbc:idontexist:", SQLDialect.DEFAULT);
}
@Test
void getSqlDialectWhenDerbyShouldReturnDerby() throws Exception {
testGetSqlDialect("jdbc:derby:", SQLDialect.DERBY);
}
@Test
void getSqlDialectWhenH2ShouldReturnH2() throws Exception {
testGetSqlDialect("jdbc:h2:", SQLDialect.H2);
}
@Test
void getSqlDialectWhenHsqldbShouldReturnHsqldb() throws Exception {
testGetSqlDialect("jdbc:hsqldb:", SQLDialect.HSQLDB);
}
@Test
void getSqlDialectWhenMysqlShouldReturnMysql() throws Exception {
testGetSqlDialect("jdbc:mysql:", SQLDialect.MYSQL);
}
@Test
void getSqlDialectWhenOracleShouldReturnDefault() throws Exception {
testGetSqlDialect("jdbc:oracle:", SQLDialect.DEFAULT);
}
@Test
void getSqlDialectWhenPostgresShouldReturnPostgres() throws Exception {
testGetSqlDialect("jdbc:postgresql:", SQLDialect.POSTGRES);
}
@Test
void getSqlDialectWhenSqlserverShouldReturnDefault() throws Exception {
testGetSqlDialect("jdbc:sqlserver:", SQLDialect.DEFAULT);
}
@Test
void getSqlDialectWhenDb2ShouldReturnDefault() throws Exception {
testGetSqlDialect("jdbc:db2:", SQLDialect.DEFAULT);
}
@Test
void getSqlDialectWhenInformixShouldReturnDefault() 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);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -40,6 +40,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.FilteredClassLoader;
@ -383,6 +384,25 @@ class LiquibaseAutoConfigurationTests {
.run(assertLiquibase((liquibase) -> assertThat(liquibase.getTag()).isEqualTo("1.0.0")));
}
@Test
void whenLiquibaseIsAutoConfiguredThenJooqDslContextDependsOnSpringLiquibaseBeans() {
this.contextRunner.withConfiguration(AutoConfigurations.of(JooqAutoConfiguration.class))
.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext");
assertThat(beanDefinition.getDependsOn()).containsExactly("liquibase");
});
}
@Test
void whenCustomSpringLiquibaseIsDefinedThenJooqDslContextDependsOnSpringLiquibaseBeans() {
this.contextRunner.withConfiguration(AutoConfigurations.of(JooqAutoConfiguration.class))
.withUserConfiguration(LiquibaseUserConfiguration.class, EmbeddedDataSourceConfiguration.class)
.run((context) -> {
BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext");
assertThat(beanDefinition.getDependsOn()).containsExactly("springLiquibase");
});
}
private ContextConsumer<AssertableApplicationContext> assertLiquibase(Consumer<SpringLiquibase> consumer) {
return (context) -> {
assertThat(context).hasSingleBean(SpringLiquibase.class);

@ -652,6 +652,19 @@ bom {
]
}
}
library("jOOQ", "3.16.0") {
group("org.jooq") {
modules = [
"jooq",
"jooq-codegen",
"jooq-kotlin",
"jooq-meta"
]
plugins = [
"jooq-codegen-maven"
]
}
}
library("Json Path", "2.6.0") {
group("com.jayway.jsonpath") {
modules = [

@ -102,6 +102,9 @@ dependencies {
exclude group: "javax.xml.bind", module: "jaxb-api"
exclude group: "org.jboss.spec.javax.transaction", module: "jboss-transaction-api_1.2_spec"
}
implementation("org.jooq:jooq") {
exclude group: "javax.xml.bind", module: "jaxb-api"
}
implementation("org.mockito:mockito-core")
implementation("org.mongodb:mongodb-driver-sync")
implementation("org.quartz-scheduler:quartz")

@ -230,6 +230,11 @@ boot-features-spring-data-jpa-repositories=features.sql.jpa-and-spring-data.repo
boot-features-creating-and-dropping-jpa-databases=features.sql.jpa-and-spring-data.creating-and-dropping
boot-features-jpa-in-web-environment=features.sql.jpa-and-spring-data.open-entity-manager-in-view
boot-features-data-jdbc=features.sql.jdbc
boot-features-jooq=features.sql.jooq
boot-features-jooq-codegen=features.sql.jooq.codegen
boot-features-jooq-dslcontext=features.sql.jooq.dslcontext
boot-features-jooq-sqldialect=features.sql.jooq.sqldialect
boot-features-jooq-customizing=features.sql.jooq.customizing
boot-features-r2dbc=features.sql.r2dbc
boot-features-r2dbc-embedded-database=features.sql.r2dbc.embedded
boot-features-r2dbc-using-database-client=features.sql.r2dbc.using-database-client
@ -333,6 +338,7 @@ boot-features-testing-spring-boot-applications-testing-autoconfigured-cassandra-
boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test=features.testing.spring-boot-applications.autoconfigured-spring-data-jpa
boot-features-testing-spring-boot-applications-testing-autoconfigured-jdbc-test=features.testing.spring-boot-applications.autoconfigured-jdbc
boot-features-testing-spring-boot-applications-testing-autoconfigured-data-jdbc-test=features.testing.spring-boot-applications.autoconfigured-spring-data-jdbc
boot-features-testing-spring-boot-applications-testing-autoconfigured-jooq-test=features.testing.spring-boot-applications.autoconfigured-jooq
boot-features-testing-spring-boot-applications-testing-autoconfigured-mongo-test=features.testing.spring-boot-applications.autoconfigured-spring-data-mongodb
boot-features-testing-spring-boot-applications-testing-autoconfigured-neo4j-test=features.testing.spring-boot-applications.autoconfigured-spring-data-neo4j
boot-features-testing-spring-boot-applications-testing-autoconfigured-redis-test=features.testing.spring-boot-applications.autoconfigured-spring-data-redis
@ -637,6 +643,7 @@ howto-use-spring-data-jpa--and-mongo-repositories=howto.data-access.use-spring-d
howto-use-customize-spring-datas-web-support=howto.data-access.customize-spring-data-web-support
howto-use-exposing-spring-data-repositories-rest-endpoint=howto.data-access.exposing-spring-data-repositories-as-rest
howto-configure-a-component-that-is-used-by-JPA=howto.data-access.configure-a-component-that-is-used-by-jpa
howto-configure-jOOQ-with-multiple-datasources=howto.data-access.configure-jooq-with-multiple-datasources
howto-database-initialization=howto.data-initialization
howto-initialize-a-database-using-jpa=howto.data-initialization.using-jpa
howto-initialize-a-database-using-hibernate=howto.data-initialization.using-hibernate
@ -909,6 +916,11 @@ features.sql.jpa-and-spring-data.open-entity-manager-in-view=data.sql.jpa-and-sp
features.sql.jdbc=data.sql.jdbc
features.sql.h2-web-console=data.sql.h2-web-console
features.sql.h2-web-console.custom-path=data.sql.h2-web-console.custom-path
features.sql.jooq=data.sql.jooq
features.sql.jooq.codegen=data.sql.jooq.codegen
features.sql.jooq.dslcontext=data.sql.jooq.dslcontext
features.sql.jooq.sqldialect=data.sql.jooq.sqldialect
features.sql.jooq.customizing=data.sql.jooq.customizing
features.sql.r2dbc=data.sql.r2dbc
features.sql.r2dbc.embedded=data.sql.r2dbc.embedded
features.sql.r2dbc.using-database-client=data.sql.r2dbc.using-database-client

@ -98,6 +98,7 @@
:gradle-docs: https://docs.gradle.org/current/userguide
:hibernate-docs: https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html
:java-api: https://docs.oracle.com/javase/8/docs/api
:jooq-docs: https://www.jooq.org/doc/{jooq-version}/manual-single-page
:junit5-docs: https://junit.org/junit5/docs/current/user-guide
:kotlin-docs: https://kotlinlang.org/docs/reference/
:lettuce-docs: https://lettuce.io/core/{lettuce-version}/reference/index.html

@ -315,6 +315,90 @@ TIP: For complete details of Spring Data JDBC, see the {spring-data-jdbc-docs}[r
[[data.sql.jooq]]
=== Using jOOQ
jOOQ Object Oriented Querying (https://www.jooq.org/[jOOQ]) is a popular product from https://www.datageekery.com/[Data Geekery] which generates Java code from your database and lets you build type-safe SQL queries through its fluent API.
Both the commercial and open source editions can be used with Spring Boot.
[[data.sql.jooq.codegen]]
==== Code Generation
In order to use jOOQ type-safe queries, you need to generate Java classes from your database schema.
You can follow the instructions in the {jooq-docs}/#jooq-in-7-steps-step3[jOOQ user manual].
If you use the `jooq-codegen-maven` plugin and you also use the `spring-boot-starter-parent` "`parent POM`", you can safely omit the plugin's `<version>` tag.
You can also use Spring Boot-defined version variables (such as `h2.version`) to declare the plugin's database dependency.
The following listing shows an example:
[source,xml,indent=0,subs="verbatim"]
----
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<executions>
...
</executions>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
</dependencies>
<configuration>
<jdbc>
<driver>org.h2.Driver</driver>
<url>jdbc:h2:~/yourdatabase</url>
</jdbc>
<generator>
...
</generator>
</configuration>
</plugin>
----
[[data.sql.jooq.dslcontext]]
==== Using DSLContext
The fluent API offered by jOOQ is initiated through the `org.jooq.DSLContext` interface.
Spring Boot auto-configures a `DSLContext` as a Spring Bean and connects it to your application `DataSource`.
To use the `DSLContext`, you can inject it, as shown in the following example:
[source,java,indent=0,subs="verbatim"]
----
include::{docs-java}/data/sql/jooq/dslcontext/MyBean.java[tag=!method]
----
TIP: The jOOQ manual tends to use a variable named `create` to hold the `DSLContext`.
You can then use the `DSLContext` to construct your queries, as shown in the following example:
[source,java,indent=0,subs="verbatim"]
----
include::{docs-java}/data/sql/jooq/dslcontext/MyBean.java[tag=method]
----
[[data.sql.jooq.sqldialect]]
==== jOOQ SQL Dialect
Unless the configprop:spring.jooq.sql-dialect[] property has been configured, Spring Boot determines the SQL dialect to use for your datasource.
If Spring Boot could not detect the dialect, it uses `DEFAULT`.
NOTE: Spring Boot can only auto-configure dialects supported by the open source version of jOOQ.
[[data.sql.jooq.customizing]]
==== Customizing jOOQ
More advanced customizations can be achieved by defining your own `DefaultConfigurationCustomizer` bean that will be invoked prior to creating the `org.jooq.Configuration` `@Bean`.
This takes precedence to anything that is applied by the auto-configuration.
You can also create your own `org.jooq.Configuration` `@Bean` if you want to take complete control of the jOOQ configuration.
[[data.sql.r2dbc]]
=== Using R2DBC
The Reactive Relational Database Connectivity (https://r2dbc.io[R2DBC]) project brings reactive programming APIs to relational databases.

@ -255,7 +255,7 @@ Spring Boot includes the following pre-defined logging groups that can be used o
| `org.springframework.core.codec`, `org.springframework.http`, `org.springframework.web`, `org.springframework.boot.actuate.endpoint.web`, `org.springframework.boot.web.servlet.ServletContextInitializerBeans`
| sql
| `org.springframework.jdbc.core`, `org.hibernate.SQL`
| `org.springframework.jdbc.core`, `org.hibernate.SQL`, `org.jooq.tools.LoggerListener`
|===

@ -553,6 +553,30 @@ If you prefer your test to run against a real database, you can use the `@AutoCo
[[features.testing.spring-boot-applications.autoconfigured-jooq]]
==== Auto-configured jOOQ Tests
You can use `@JooqTest` in a similar fashion as `@JdbcTest` but for jOOQ-related tests.
As jOOQ relies heavily on a Java-based schema that corresponds with the database schema, the existing `DataSource` is used.
If you want to replace it with an in-memory database, you can use `@AutoConfigureTestDatabase` to override those settings.
(For more about using jOOQ with Spring Boot, see "<<data#data.sql.jooq>>", earlier in this chapter.)
Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@JooqTest` annotation is used.
`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans.
TIP: A list of the auto-configurations that are enabled by `@JooqTest` can be <<test-auto-configuration#test-auto-configuration,found in the appendix>>.
`@JooqTest` configures a `DSLContext`.
The following example shows the `@JooqTest` annotation in use:
[source,java,indent=0,subs="verbatim"]
----
include::{docs-java}/features/testing/springbootapplications/autoconfiguredjooq/MyJooqTests.java[]
----
JOOQ tests are transactional and roll back at the end of each test by default.
If that is not what you want, you can disable transaction management for a test or for the whole test class as <<features#features.testing.spring-boot-applications.autoconfigured-jdbc,shown in the JDBC example>>.
[[features.testing.spring-boot-applications.autoconfigured-spring-data-mongodb]]
==== Auto-configured Data MongoDB Tests
You can use `@DataMongoTest` to test MongoDB applications.

@ -394,3 +394,12 @@ For example, if you use Hibernate Search with Elasticsearch as its index manager
----
include::{docs-java}/howto/dataaccess/configureacomponentthatisusedbyjpa/ElasticsearchEntityManagerFactoryDependsOnPostProcessor.java[]
----
[[howto.data-access.configure-jooq-with-multiple-datasources]]
=== Configure jOOQ with Two DataSources
If you need to use jOOQ with multiple data sources, you should create your own `DSLContext` for each one.
See {spring-boot-autoconfigure-module-code}/jooq/JooqAutoConfiguration.java[JooqAutoConfiguration] for more details.
TIP: In particular, `JooqExceptionTranslator` and `SpringTransactionProvider` can be reused to provide similar features to what the auto-configuration does with a single `DataSource`.

@ -209,6 +209,7 @@ To have other beans be detected, register an implementation of `DatabaseInitiali
Spring Boot will automatically detect beans of the following types that depends upon database initialization:
- `AbstractEntityManagerFactoryBean` (unless configprop:spring.jpa.defer-datasource-initialization[] is set to `true`)
- `DSLContext` (jOOQ)
- `EntityManagerFactory` (unless configprop:spring.jpa.defer-datasource-initialization[] is set to `true`)
- `JdbcOperations`
- `NamedParameterJdbcOperations`

@ -0,0 +1,46 @@
/*
* Copyright 2012-2022 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
*
* https://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.docs.data.sql.jooq.dslcontext;
import java.util.GregorianCalendar;
import java.util.List;
import org.jooq.DSLContext;
import org.springframework.stereotype.Component;
import static org.springframework.boot.docs.data.sql.jooq.dslcontext.Tables.AUTHOR;
@Component
public class MyBean {
private final DSLContext create;
public MyBean(DSLContext dslContext) {
this.create = dslContext;
}
// tag::method[]
public List<GregorianCalendar> authorsBornAfter1980() {
// @formatter:off
return this.create.selectFrom(AUTHOR)
.where(AUTHOR.DATE_OF_BIRTH.greaterThan(new GregorianCalendar(1980, 0, 1)))
.fetch(AUTHOR.DATE_OF_BIRTH);
// @formatter:on
} // end::method[]
}

@ -0,0 +1,49 @@
/*
* Copyright 2012-2022 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
*
* https://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.docs.data.sql.jooq.dslcontext;
import java.util.GregorianCalendar;
import org.jooq.Name;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.impl.TableImpl;
import org.jooq.impl.TableRecordImpl;
abstract class Tables {
static final TAuthor AUTHOR = null;
abstract class TAuthor extends TableImpl<TAuthorRecord> {
TAuthor(Name name) {
super(name);
}
public final TableField<TAuthorRecord, GregorianCalendar> DATE_OF_BIRTH = null;
}
abstract class TAuthorRecord extends TableRecordImpl<TAuthorRecord> {
TAuthorRecord(Table<TAuthorRecord> table) {
super(table);
}
}
}

@ -0,0 +1,33 @@
/*
* Copyright 2012-2022 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
*
* https://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.docs.features.testing.springbootapplications.autoconfiguredjooq;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jooq.JooqTest;
@JooqTest
class MyJooqTests {
@Autowired
@SuppressWarnings("unused")
private DSLContext dslContext;
// ...
}

@ -0,0 +1,11 @@
plugins {
id "org.springframework.boot.starter"
}
description = "Starter for using jOOQ to access SQL databases with JDBC. An alternative to spring-boot-starter-data-jpa or spring-boot-starter-jdbc"
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jdbc"))
api("org.springframework:spring-tx")
api("org.jooq:jooq")
}

@ -78,6 +78,9 @@ dependencies {
testImplementation("org.eclipse:yasson")
testImplementation("org.hibernate.validator:hibernate-validator")
testImplementation("org.hsqldb:hsqldb")
testImplementation("org.jooq:jooq") {
exclude group: "javax.xml.bind", module: "jaxb-api"
}
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.junit.platform:junit-platform-engine")
testImplementation("org.junit.platform:junit-platform-launcher")

@ -0,0 +1,43 @@
/*
* Copyright 2012-2022 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
*
* https://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.test.autoconfigure.jooq;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
/**
* {@link ImportAutoConfiguration Auto-configuration imports} for typical jOOQ tests. Most
* tests should consider using {@link JooqTest @JooqTest} rather than using this
* annotation directly.
*
* @author Michael Simons
* @since 2.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
public @interface AutoConfigureJooq {
}

@ -0,0 +1,113 @@
/*
* Copyright 2012-2022 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
*
* https://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.test.autoconfigure.jooq;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration;
import org.springframework.boot.test.autoconfigure.core.AutoConfigureCache;
import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.env.Environment;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;
/**
* Annotation for a jOOQ test that focuses <strong>only</strong> on jOOQ-based components.
* <p>
* Using this annotation will disable full auto-configuration and instead apply only
* configuration relevant to jOOQ tests.
* <p>
* By default, tests annotated with {@code @JooqTest} use the configured database. If you
* want to replace any explicit or usually auto-configured DataSource by an embedded
* in-memory database, the {@link AutoConfigureTestDatabase @AutoConfigureTestDatabase}
* annotation can be used to override these settings.
* <p>
* When using JUnit 4, this annotation should be used in combination with
* {@code @RunWith(SpringRunner.class)}.
*
* @author Michael Simons
* @author Stephane Nicoll
* @author Artsiom Yudovin
* @since 2.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(JooqTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(JooqTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureJooq
@ImportAutoConfiguration
public @interface JooqTest {
/**
* Properties in form {@literal key=value} that should be added to the Spring
* {@link Environment} before the test runs.
* @return the properties to add
* @since 2.1.0
*/
String[] properties() default {};
/**
* Determines if default filtering should be used with
* {@link SpringBootApplication @SpringBootApplication}. By default no beans are
* included.
* @see #includeFilters()
* @see #excludeFilters()
* @return if default filters should be used
*/
boolean useDefaultFilters() default true;
/**
* A set of include filters which can be used to add otherwise filtered beans to the
* application context.
* @return include filters to apply
*/
Filter[] includeFilters() default {};
/**
* A set of exclude filters which can be used to filter beans that would otherwise be
* added to the application context.
* @return exclude filters to apply
*/
Filter[] excludeFilters() default {};
/**
* Auto-configuration exclusions that should be applied for this test.
* @return auto-configuration exclusions to apply
*/
@AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
Class<?>[] excludeAutoConfiguration() default {};
}

@ -0,0 +1,37 @@
/*
* Copyright 2012-2022 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
*
* https://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.test.autoconfigure.jooq;
import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.test.context.TestContextBootstrapper;
/**
* {@link TestContextBootstrapper} for {@link JooqTest @JooqTest} support.
*
* @author Artsiom Yudovin
*/
class JooqTestContextBootstrapper extends SpringBootTestContextBootstrapper {
@Override
protected String[] getProperties(Class<?> testClass) {
return MergedAnnotations.from(testClass, SearchStrategy.INHERITED_ANNOTATIONS).get(JooqTest.class)
.getValue("properties", String[].class).orElse(null);
}
}

@ -0,0 +1,34 @@
/*
* Copyright 2012-2022 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
*
* https://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.test.autoconfigure.jooq;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.test.autoconfigure.filter.StandardAnnotationCustomizableTypeExcludeFilter;
/**
* {@link TypeExcludeFilter} for {@link JooqTest @JooqTest}.
*
* @author Michael Simons
* @since 2.2.1
*/
public final class JooqTypeExcludeFilter extends StandardAnnotationCustomizableTypeExcludeFilter<JooqTest> {
JooqTypeExcludeFilter(Class<?> testClass) {
super(testClass);
}
}

@ -0,0 +1,20 @@
/*
* Copyright 2012-2022 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
*
* https://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.
*/
/**
* Auto-configuration for jOOQ tests.
*/
package org.springframework.boot.test.autoconfigure.jooq;

@ -0,0 +1,40 @@
/*
* Copyright 2012-2022 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
*
* https://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.test.autoconfigure.jooq;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
/**
* Example {@link SpringBootApplication @SpringBootApplication} used with
* {@link JooqTest @JooqTest} tests.
*
* @author Michael Simons
*/
@SpringBootApplication
public class ExampleJooqApplication {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().generateUniqueName(true).setType(EmbeddedDatabaseType.HSQL).build();
}
}

@ -0,0 +1,88 @@
/*
* Copyright 2012-2022 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
*
* https://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.test.autoconfigure.jooq;
import javax.sql.DataSource;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.test.autoconfigure.orm.jpa.ExampleComponent;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.boot.test.autoconfigure.AutoConfigurationImportedCondition.importedAutoConfiguration;
/**
* Integration tests for {@link JooqTest @JooqTest}.
*
* @author Michael Simons
*/
@JooqTest
class JooqTestIntegrationTests {
@Autowired
private DSLContext dsl;
@Autowired
private DataSource dataSource;
@Autowired
private ApplicationContext applicationContext;
@Test
void testDSLContext() {
assertThat(this.dsl.selectCount().from("INFORMATION_SCHEMA.TABLES").fetchOne(0, Integer.class))
.isGreaterThan(0);
}
@Test
void useDefinedDataSource() throws Exception {
String product = this.dataSource.getConnection().getMetaData().getDatabaseProductName();
assertThat(product).startsWith("HSQL");
assertThat(this.dsl.configuration().dialect()).isEqualTo(SQLDialect.HSQLDB);
}
@Test
void didNotInjectExampleComponent() {
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.applicationContext.getBean(ExampleComponent.class));
}
@Test
void flywayAutoConfigurationWasImported() {
assertThat(this.applicationContext).has(importedAutoConfiguration(FlywayAutoConfiguration.class));
}
@Test
void liquibaseAutoConfigurationWasImported() {
assertThat(this.applicationContext).has(importedAutoConfiguration(LiquibaseAutoConfiguration.class));
}
@Test
void cacheAutoConfigurationWasImported() {
assertThat(this.applicationContext).has(importedAutoConfiguration(CacheAutoConfiguration.class));
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2012-2022 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
*
* https://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.test.autoconfigure.jooq;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for the {@link JooqTest#properties properties} attribute of
* {@link JooqTest @JooqTest}.
*
* @author Artsiom Yudovin
*/
@JooqTest(properties = "spring.profiles.active=test")
class JooqTestPropertiesIntegrationTests {
@Autowired
private Environment environment;
@Test
void environmentWithNewProfile() {
assertThat(this.environment.getActiveProfiles()).containsExactly("test");
}
}

@ -0,0 +1,53 @@
/*
* Copyright 2012-2022 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
*
* https://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.test.autoconfigure.jooq;
import javax.sql.DataSource;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link JooqTest @JooqTest}.
*
* @author Stephane Nicoll
*/
@JooqTest
@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
class JooqTestWithAutoConfigureTestDatabaseIntegrationTests {
@Autowired
private DSLContext dsl;
@Autowired
private DataSource dataSource;
@Test
void replacesAutoConfiguredDataSource() throws Exception {
String product = this.dataSource.getConnection().getMetaData().getDatabaseProductName();
assertThat(product).startsWith("H2");
assertThat(this.dsl.configuration().dialect()).isEqualTo(SQLDialect.H2);
}
}

@ -73,6 +73,9 @@ dependencies {
optional("org.hamcrest:hamcrest-library")
optional("org.hibernate:hibernate-core-jakarta")
optional("org.hibernate.validator:hibernate-validator")
optional("org.jooq:jooq") {
exclude(group: "javax.xml.bind", module: "jaxb-api")
}
optional("org.liquibase:liquibase-core") {
exclude(group: "javax.xml.bind", module: "jaxb-api")
}

@ -0,0 +1,39 @@
/*
* Copyright 2012-2022 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
*
* https://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.jooq;
import java.util.Collections;
import java.util.Set;
import org.jooq.DSLContext;
import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDependsOnDatabaseInitializationDetector;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector;
/**
* {@link DependsOnDatabaseInitializationDetector} for jOOQ.
*
* @author Andy Wilkinson
*/
class JooqDependsOnDatabaseInitializationDetector extends AbstractBeansOfTypeDependsOnDatabaseInitializationDetector {
@Override
protected Set<Class<?>> getDependsOnDatabaseInitializationBeanTypes() {
return Collections.singleton(DSLContext.class);
}
}

@ -0,0 +1,22 @@
/*
* Copyright 2012-2022 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
*
* https://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.
*/
/**
* Support for jOOQ.
*
* @see org.springframework.boot.json.JsonParser
*/
package org.springframework.boot.jooq;

@ -93,4 +93,5 @@ org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializerDetector
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector,\
org.springframework.boot.jdbc.SpringJdbcDependsOnDatabaseInitializationDetector,\
org.springframework.boot.jooq.JooqDependsOnDatabaseInitializationDetector,\
org.springframework.boot.orm.jpa.JpaDependsOnDatabaseInitializationDetector

Loading…
Cancel
Save