Add configuration property to specify jOOQ settings file
The property is named 'spring.jooq.config' and is of type Resource, so that it supports classpath: and all the other common prefixes. The config is loaded through JAXB. If JAXB is not on the classpath, an exception is thrown. Also adds a failure analyzer for this exception. Closes gh-38778
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
|
||||
/**
|
||||
* Exception to be thrown if JAXB is not available.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class JaxbNotAvailableException extends RuntimeException {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.springframework.boot.diagnostics.AbstractFailureAnalyzer;
|
||||
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||
import org.springframework.boot.diagnostics.FailureAnalyzer;
|
||||
|
||||
/**
|
||||
* {@link FailureAnalyzer} for {@link JaxbNotAvailableException}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class JaxbNotAvailableExceptionFailureAnalyzer extends AbstractFailureAnalyzer<JaxbNotAvailableException> {
|
||||
|
||||
@Override
|
||||
protected FailureAnalysis analyze(Throwable rootFailure, JaxbNotAvailableException cause) {
|
||||
return new FailureAnalysis("Unable to unmarshal jOOQ settings because JAXB is not available.",
|
||||
"Add JAXB to the classpath or remove the spring.jooq.config property.", cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,16 +16,30 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.jooq;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.sax.SAXSource;
|
||||
|
||||
import jakarta.xml.bind.JAXBContext;
|
||||
import jakarta.xml.bind.JAXBException;
|
||||
import jakarta.xml.bind.Unmarshaller;
|
||||
import org.jooq.ConnectionProvider;
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.ExecuteListenerProvider;
|
||||
import org.jooq.TransactionProvider;
|
||||
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.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
@@ -33,20 +47,25 @@ 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.condition.ConditionalOnProperty;
|
||||
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.core.annotation.Order;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for JOOQ.
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for jOOQ.
|
||||
*
|
||||
* @author Andreas Ahlenstorf
|
||||
* @author Michael Simons
|
||||
* @author Dmytro Nosan
|
||||
* @author Moritz Halbritter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class })
|
||||
@@ -89,17 +108,57 @@ public class JooqAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(org.jooq.Configuration.class)
|
||||
public DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider,
|
||||
DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider,
|
||||
DataSource dataSource, ObjectProvider<TransactionProvider> transactionProvider,
|
||||
ObjectProvider<ExecuteListenerProvider> executeListenerProviders,
|
||||
ObjectProvider<DefaultConfigurationCustomizer> configurationCustomizers) {
|
||||
ObjectProvider<DefaultConfigurationCustomizer> configurationCustomizers,
|
||||
ObjectProvider<Settings> settingsProvider) {
|
||||
DefaultConfiguration configuration = new DefaultConfiguration();
|
||||
configuration.set(properties.determineSqlDialect(dataSource));
|
||||
configuration.set(connectionProvider);
|
||||
transactionProvider.ifAvailable(configuration::set);
|
||||
settingsProvider.ifAvailable(configuration::set);
|
||||
configuration.set(executeListenerProviders.orderedStream().toArray(ExecuteListenerProvider[]::new));
|
||||
configurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty("spring.jooq.config")
|
||||
@ConditionalOnMissingBean(Settings.class)
|
||||
Settings settings(JooqProperties properties) throws IOException {
|
||||
if (!ClassUtils.isPresent("jakarta.xml.bind.JAXBContext", null)) {
|
||||
throw new JaxbNotAvailableException();
|
||||
}
|
||||
Resource resource = properties.getConfig();
|
||||
Assert.state(resource.exists(),
|
||||
() -> "Resource %s set in spring.jooq.config does not exist".formatted(resource));
|
||||
try (InputStream stream = resource.getInputStream()) {
|
||||
return new JaxbSettingsLoader().load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class JaxbSettingsLoader {
|
||||
|
||||
private Settings load(InputStream inputStream) {
|
||||
try {
|
||||
// See
|
||||
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#jaxb-unmarshaller
|
||||
SAXParserFactory spf = SAXParserFactory.newInstance();
|
||||
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
spf.setNamespaceAware(true);
|
||||
spf.setXIncludeAware(false);
|
||||
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(inputStream));
|
||||
JAXBContext jc = JAXBContext.newInstance(Settings.class);
|
||||
Unmarshaller um = jc.createUnmarshaller();
|
||||
return um.unmarshal(xmlSource, Settings.class).getValue();
|
||||
}
|
||||
catch (ParserConfigurationException | JAXBException | SAXException ex) {
|
||||
throw new IllegalStateException("Failed to unmarshal settings", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,12 +21,14 @@ import javax.sql.DataSource;
|
||||
import org.jooq.SQLDialect;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Configuration properties for the JOOQ database library.
|
||||
* Configuration properties for the jOOQ database library.
|
||||
*
|
||||
* @author Andreas Ahlenstorf
|
||||
* @author Michael Simons
|
||||
* @author Moritz Halbritter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.jooq")
|
||||
@@ -37,6 +39,11 @@ public class JooqProperties {
|
||||
*/
|
||||
private SQLDialect sqlDialect;
|
||||
|
||||
/**
|
||||
* Location of the jOOQ config file.
|
||||
*/
|
||||
private Resource config;
|
||||
|
||||
public SQLDialect getSqlDialect() {
|
||||
return this.sqlDialect;
|
||||
}
|
||||
@@ -45,6 +52,14 @@ public class JooqProperties {
|
||||
this.sqlDialect = sqlDialect;
|
||||
}
|
||||
|
||||
public Resource getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
public void setConfig(Resource config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the {@link SQLDialect} to use based on this configuration and the primary
|
||||
* {@link DataSource}.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -21,7 +21,7 @@ import org.jooq.Transaction;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
|
||||
/**
|
||||
* Adapts a Spring transaction for JOOQ.
|
||||
* Adapts a Spring transaction for jOOQ.
|
||||
*
|
||||
* @author Lukas Eder
|
||||
* @author Andreas Ahlenstorf
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -25,7 +25,7 @@ import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
|
||||
/**
|
||||
* Allows Spring Transaction to be used with JOOQ.
|
||||
* Allows Spring Transaction to be used with jOOQ.
|
||||
*
|
||||
* @author Lukas Eder
|
||||
* @author Andreas Ahlenstorf
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for JOOQ.
|
||||
* Auto-configuration for jOOQ.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.jooq;
|
||||
|
||||
@@ -27,6 +27,7 @@ org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,
|
||||
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
|
||||
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
|
||||
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
|
||||
org.springframework.boot.autoconfigure.jooq.JaxbNotAvailableExceptionFailureAnalyzer,\
|
||||
org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
|
||||
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
|
||||
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jooq.SQLDialect;
|
||||
import org.jooq.TransactionContext;
|
||||
import org.jooq.TransactionProvider;
|
||||
import org.jooq.TransactionalRunnable;
|
||||
import org.jooq.conf.Settings;
|
||||
import org.jooq.impl.DataSourceConnectionProvider;
|
||||
import org.jooq.impl.DefaultDSLContext;
|
||||
import org.jooq.impl.DefaultExecuteListenerProvider;
|
||||
@@ -35,6 +36,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -57,6 +59,7 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Stephane Nicoll
|
||||
* @author Dmytro Nosan
|
||||
* @author Dennis Melzer
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class JooqAutoConfigurationTests {
|
||||
|
||||
@@ -221,6 +224,37 @@ class JooqAutoConfigurationTests {
|
||||
.run((context) -> assertThat(context).hasSingleBean(DSLContext.class).hasBean("customDslContext"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoadSettingsFromConfigPropertyThroughJaxb() {
|
||||
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.jooq.config=classpath:org/springframework/boot/autoconfigure/jooq/settings.xml")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(Settings.class);
|
||||
Settings settings = context.getBean(Settings.class);
|
||||
assertThat(settings.getBatchSize()).isEqualTo(100);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotProvideSettingsIfJaxbIsMissing() {
|
||||
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
|
||||
.withClassLoader(new FilteredClassLoader("jakarta.xml.bind"))
|
||||
.withPropertyValues("spring.jooq.config=classpath:org/springframework/boot/autoconfigure/jooq/settings.xml")
|
||||
.run((context) -> assertThat(context).hasFailed()
|
||||
.getFailure()
|
||||
.hasRootCauseInstanceOf(JaxbNotAvailableException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithSensibleErrorMessageIfConfigIsNotFound() {
|
||||
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.jooq.config=classpath:does-not-exist.xml")
|
||||
.run((context) -> assertThat(context).hasFailed()
|
||||
.getFailure()
|
||||
.hasMessageContaining("spring.jooq.config")
|
||||
.hasMessageContaining("does-not-exist.xml"));
|
||||
}
|
||||
|
||||
static class AssertFetch implements TransactionalRunnable {
|
||||
|
||||
private final DSLContext dsl;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.19.0.xsd">
|
||||
<batchSize>100</batchSize>
|
||||
</settings>
|
||||
@@ -4,3 +4,4 @@
|
||||
^http://www.jdotsoft.com.*
|
||||
^http://www.liquibase.org/xml/ns/dbchangelog/.*
|
||||
^http://www.w3.org/2000/09/xmldsig.*
|
||||
^http://www.jooq.org/xsd/jooq-runtime-.*
|
||||
|
||||
Reference in New Issue
Block a user