diff --git a/jooq/src/main/java/io/micronaut/configuration/jooq/AbstractJooqConfigurationFactory.java b/jooq/src/main/java/io/micronaut/configuration/jooq/AbstractJooqConfigurationFactory.java index 0c513943e..d1b57a5a7 100644 --- a/jooq/src/main/java/io/micronaut/configuration/jooq/AbstractJooqConfigurationFactory.java +++ b/jooq/src/main/java/io/micronaut/configuration/jooq/AbstractJooqConfigurationFactory.java @@ -60,7 +60,8 @@ abstract class AbstractJooqConfigurationFactory { * @return A {@link org.jooq.Configuration} */ @SuppressWarnings("checkstyle:ParameterNumber") - protected DefaultConfiguration jooqConfiguration( + protected T configureConfiguration( + T configuration, @Parameter String name, @Parameter @Nullable TransactionProvider transactionProvider, @Parameter @Nullable Settings settings, @@ -72,8 +73,6 @@ protected DefaultConfiguration jooqConfiguration( AbstractJooqConfigurationProperties properties, ApplicationContext ctx ) { - DefaultConfiguration configuration = new DefaultConfiguration(); - if (transactionProvider != null) { configuration.setTransactionProvider(transactionProvider); } diff --git a/jooq/src/main/java/io/micronaut/configuration/jooq/DSLContextFactory.java b/jooq/src/main/java/io/micronaut/configuration/jooq/DSLContextFactory.java index 3dc7dd2ec..9b2cadc4c 100644 --- a/jooq/src/main/java/io/micronaut/configuration/jooq/DSLContextFactory.java +++ b/jooq/src/main/java/io/micronaut/configuration/jooq/DSLContextFactory.java @@ -15,11 +15,19 @@ */ package io.micronaut.configuration.jooq; +import io.micronaut.context.BeanProvider; +import io.micronaut.context.annotation.Any; import io.micronaut.context.annotation.EachBean; import io.micronaut.context.annotation.Factory; +import io.micronaut.context.annotation.Parameter; +import io.micronaut.context.annotation.Primary; +import io.micronaut.context.annotation.Requires; +import io.micronaut.context.annotation.Secondary; import io.micronaut.core.annotation.Internal; +import io.micronaut.inject.qualifiers.Qualifiers; import io.micronaut.transaction.TransactionStatus; import io.micronaut.transaction.support.ExceptionUtil; +import jakarta.inject.Singleton; import org.jooq.Configuration; import org.jooq.DSLContext; import org.jooq.TransactionProperty; @@ -43,8 +51,47 @@ final class DSLContextFactory { * @param configuration The {@link Configuration} * @return A {@link DSLContext} */ - @EachBean(Configuration.class) - DSLContext dslContext(Configuration configuration) { + @EachBean(JdbcConfiguration.class) + @Secondary + DSLContext dslContext(@Parameter JdbcConfiguration configuration) { + return createDslContext(configuration); + } + + @EachBean(R2dbcConfiguration.class) + @Secondary + DSLContext r2dbcDslContext(@Parameter R2dbcConfiguration configuration) { + return createDslContext(configuration); + } + + @Primary + @Singleton + @Requires(beans = R2dbcConfiguration.class) + DSLContext primaryR2dbcDslContext(@Any BeanProvider configurations) { + R2dbcConfiguration configuration = selectR2dbcConfiguration(configurations); + return createDslContext(configuration); + } + + private R2dbcConfiguration selectR2dbcConfiguration(BeanProvider configurations) { + return configurations.find(Qualifiers.byStereotype(Primary.class)) + .or(() -> configurations.find(Qualifiers.byName("default"))) + .orElseGet(() -> requireSingleConfiguration(configurations)); + } + + private R2dbcConfiguration requireSingleConfiguration(BeanProvider configurations) { + var iterator = configurations.iterator(); + var configuration = iterator.next(); + if (iterator.hasNext()) { + int candidateCount = 2; + while (iterator.hasNext()) { + iterator.next(); + candidateCount++; + } + throw new IllegalStateException("Multiple R2DBC configurations found (" + candidateCount + "). Mark one @Primary or name one 'default'"); + } + return configuration; + } + + private DSLContext createDslContext(Configuration configuration) { return new DefaultDSLContext(configuration) { @Override diff --git a/jooq/src/main/java/io/micronaut/configuration/jooq/JdbcConfiguration.java b/jooq/src/main/java/io/micronaut/configuration/jooq/JdbcConfiguration.java new file mode 100644 index 000000000..e80bf647d --- /dev/null +++ b/jooq/src/main/java/io/micronaut/configuration/jooq/JdbcConfiguration.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017-2026 original 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 io.micronaut.configuration.jooq; + +import io.micronaut.core.annotation.Internal; +import org.jooq.impl.DefaultConfiguration; + +/** + * Internal JDBC-backed jOOQ configuration bean type. + * + * @since 7.0.0 + */ +@Internal +final class JdbcConfiguration extends DefaultConfiguration { +} diff --git a/jooq/src/main/java/io/micronaut/configuration/jooq/JooqConfigurationFactory.java b/jooq/src/main/java/io/micronaut/configuration/jooq/JooqConfigurationFactory.java index 58968407f..fae215ad7 100644 --- a/jooq/src/main/java/io/micronaut/configuration/jooq/JooqConfigurationFactory.java +++ b/jooq/src/main/java/io/micronaut/configuration/jooq/JooqConfigurationFactory.java @@ -20,7 +20,6 @@ import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Parameter; import io.micronaut.core.annotation.Internal; -import org.jspecify.annotations.Nullable; import io.micronaut.jdbc.DataSourceResolver; import org.jooq.Configuration; import org.jooq.ConnectionProvider; @@ -31,7 +30,7 @@ import org.jooq.RecordUnmapperProvider; import org.jooq.TransactionProvider; import org.jooq.conf.Settings; -import org.jooq.impl.DefaultConfiguration; +import org.jspecify.annotations.Nullable; import javax.sql.DataSource; @@ -66,7 +65,7 @@ final class JooqConfigurationFactory extends AbstractJooqConfigurationFactory { */ @SuppressWarnings("checkstyle:ParameterNumber") @EachBean(DataSource.class) - Configuration jooqConfiguration( + JdbcConfiguration jooqConfiguration( @Parameter String name, DataSource dataSource, @Parameter @Nullable TransactionProvider transactionProvider, @@ -89,7 +88,7 @@ Configuration jooqConfiguration( dataSourceResolver = DataSourceResolver.DEFAULT; } - DefaultConfiguration configuration = super.jooqConfiguration(name, transactionProvider, settings, executorProvider, + JdbcConfiguration configuration = super.configureConfiguration(new JdbcConfiguration(), name, transactionProvider, settings, executorProvider, recordMapperProvider, recordUnmapperProvider, metaProvider, converterProvider, properties, ctx); if (connectionProvider != null) { diff --git a/jooq/src/main/java/io/micronaut/configuration/jooq/R2dbcConfiguration.java b/jooq/src/main/java/io/micronaut/configuration/jooq/R2dbcConfiguration.java new file mode 100644 index 000000000..e691aa5ca --- /dev/null +++ b/jooq/src/main/java/io/micronaut/configuration/jooq/R2dbcConfiguration.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017-2026 original 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 io.micronaut.configuration.jooq; + +import io.micronaut.core.annotation.Internal; +import org.jooq.impl.DefaultConfiguration; + +/** + * Internal R2DBC-backed jOOQ configuration bean type. + * + * @since 7.0.0 + */ +@Internal +final class R2dbcConfiguration extends DefaultConfiguration { +} diff --git a/jooq/src/main/java/io/micronaut/configuration/jooq/R2dbcJooqConfigurationFactory.java b/jooq/src/main/java/io/micronaut/configuration/jooq/R2dbcJooqConfigurationFactory.java index 12722563a..3ba7f7dc1 100644 --- a/jooq/src/main/java/io/micronaut/configuration/jooq/R2dbcJooqConfigurationFactory.java +++ b/jooq/src/main/java/io/micronaut/configuration/jooq/R2dbcJooqConfigurationFactory.java @@ -21,7 +21,6 @@ import io.micronaut.context.annotation.Parameter; import io.micronaut.context.annotation.Requires; import io.micronaut.core.annotation.Internal; -import org.jspecify.annotations.Nullable; import io.r2dbc.spi.ConnectionFactory; import org.jooq.Configuration; import org.jooq.ConverterProvider; @@ -32,7 +31,7 @@ import org.jooq.SQLDialect; import org.jooq.TransactionProvider; import org.jooq.conf.Settings; -import org.jooq.impl.DefaultConfiguration; +import org.jspecify.annotations.Nullable; /** * Sets up R2DBC jOOQ library integration. @@ -64,7 +63,7 @@ final class R2dbcJooqConfigurationFactory extends AbstractJooqConfigurationFacto */ @SuppressWarnings("checkstyle:MethodLength") @EachBean(ConnectionFactory.class) - Configuration jooqConfiguration( + R2dbcConfiguration jooqConfiguration( @Parameter String name, ConnectionFactory connectionFactory, @Parameter @Nullable TransactionProvider transactionProvider, @@ -81,7 +80,7 @@ Configuration jooqConfiguration( properties = new R2dbcJooqConfigurationProperties(); } - DefaultConfiguration configuration = super.jooqConfiguration(name, transactionProvider, settings, executorProvider, + R2dbcConfiguration configuration = super.configureConfiguration(new R2dbcConfiguration(), name, transactionProvider, settings, executorProvider, recordMapperProvider, recordUnmapperProvider, metaProvider, converterProvider, properties, ctx); configuration.setSQLDialect(getSqlDialect(properties)); diff --git a/jooq/src/test/groovy/io/micronaut/configuration/jooq/DslContextFactorySpec.groovy b/jooq/src/test/groovy/io/micronaut/configuration/jooq/DslContextFactorySpec.groovy new file mode 100644 index 000000000..b8c2bae95 --- /dev/null +++ b/jooq/src/test/groovy/io/micronaut/configuration/jooq/DslContextFactorySpec.groovy @@ -0,0 +1,119 @@ +/* + * Copyright 2017-2022 original 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 io.micronaut.configuration.jooq + +import io.micronaut.context.ApplicationContext +import io.micronaut.context.annotation.Factory +import io.micronaut.context.annotation.Requires +import io.r2dbc.spi.ConnectionFactory +import jakarta.inject.Named +import jakarta.inject.Singleton +import org.jooq.DSLContext +import org.jooq.SQLDialect +import spock.lang.Specification + +import javax.sql.DataSource +import java.lang.reflect.Proxy + +class DslContextFactorySpec extends Specification { + + void "test default dsl context can be injected with jdbc and r2dbc configurations"() { + given: + ApplicationContext applicationContext = ApplicationContext.run("multiple-config") + + when: + TestService testService = applicationContext.getBean(TestService) + + then: + testService.dslContext != null + testService.dslContext.configuration() instanceof R2dbcConfiguration + + cleanup: + applicationContext.close() + } + + @Singleton + @Requires(env = "multiple-config") + static class TestService { + final DSLContext dslContext + + TestService(DSLContext dslContext) { + this.dslContext = dslContext + } + } + + @Factory + @Requires(env = "multiple-config") + static class TestConnectionFactory { + + @Singleton + @Named("default") + DataSource dataSource() { + return proxy(DataSource) + } + + @Singleton + @Named("default") + ConnectionFactory connectionFactory() { + return proxy(ConnectionFactory) + } + + @Singleton + @Named("analytics") + ConnectionFactory analyticsConnectionFactory() { + return proxy(ConnectionFactory) + } + + @Singleton + @Named("default") + JooqConfigurationProperties jdbcJooqProperties() { + JooqConfigurationProperties configurationProperties = new JooqConfigurationProperties() + configurationProperties.sqlDialect = SQLDialect.H2 + return configurationProperties + } + + @Singleton + @Named("default") + R2dbcJooqConfigurationProperties r2dbcJooqProperties() { + R2dbcJooqConfigurationProperties configurationProperties = new R2dbcJooqConfigurationProperties() + configurationProperties.sqlDialect = SQLDialect.POSTGRES + return configurationProperties + } + + @Singleton + @Named("analytics") + R2dbcJooqConfigurationProperties analyticsR2dbcJooqProperties() { + R2dbcJooqConfigurationProperties configurationProperties = new R2dbcJooqConfigurationProperties() + configurationProperties.sqlDialect = SQLDialect.MYSQL + return configurationProperties + } + + private static T proxy(Class type) { + return (T) Proxy.newProxyInstance(type.classLoader, [type] as Class[]) { _, method, _ -> + if (method.name == "toString") { + return type.simpleName + } + if (method.returnType == Boolean.TYPE) { + return false + } + if (method.returnType == Integer.TYPE) { + return 0 + } + return null + } + } + } +}