Skip to content

Commit cce509c

Browse files
committed
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
1 parent b9bab7a commit cce509c

File tree

11 files changed

+186
-10
lines changed

11 files changed

+186
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jooq;
18+
19+
/**
20+
* Exception to be thrown if JAXB is not available.
21+
*
22+
* @author Moritz Halbritter
23+
*/
24+
class JaxbNotAvailableException extends RuntimeException {
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jooq;
18+
19+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
20+
import org.springframework.boot.diagnostics.FailureAnalysis;
21+
import org.springframework.boot.diagnostics.FailureAnalyzer;
22+
23+
/**
24+
* {@link FailureAnalyzer} for {@link JaxbNotAvailableException}.
25+
*
26+
* @author Moritz Halbritter
27+
*/
28+
class JaxbNotAvailableExceptionFailureAnalyzer extends AbstractFailureAnalyzer<JaxbNotAvailableException> {
29+
30+
@Override
31+
protected FailureAnalysis analyze(Throwable rootFailure, JaxbNotAvailableException cause) {
32+
return new FailureAnalysis("Unable to unmarshal jOOQ settings because JAXB is not available.",
33+
"Add JAXB to the classpath or remove the spring.jooq.config property.", cause);
34+
}
35+
36+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,37 +16,56 @@
1616

1717
package org.springframework.boot.autoconfigure.jooq;
1818

19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
1922
import javax.sql.DataSource;
23+
import javax.xml.XMLConstants;
24+
import javax.xml.parsers.ParserConfigurationException;
25+
import javax.xml.parsers.SAXParserFactory;
26+
import javax.xml.transform.Source;
27+
import javax.xml.transform.sax.SAXSource;
2028

29+
import jakarta.xml.bind.JAXBContext;
30+
import jakarta.xml.bind.JAXBException;
31+
import jakarta.xml.bind.Unmarshaller;
2132
import org.jooq.ConnectionProvider;
2233
import org.jooq.DSLContext;
2334
import org.jooq.ExecuteListenerProvider;
2435
import org.jooq.TransactionProvider;
36+
import org.jooq.conf.Settings;
2537
import org.jooq.impl.DataSourceConnectionProvider;
2638
import org.jooq.impl.DefaultConfiguration;
2739
import org.jooq.impl.DefaultDSLContext;
2840
import org.jooq.impl.DefaultExecuteListenerProvider;
41+
import org.xml.sax.InputSource;
42+
import org.xml.sax.SAXException;
2943

3044
import org.springframework.beans.factory.ObjectProvider;
3145
import org.springframework.boot.autoconfigure.AutoConfiguration;
3246
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3347
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3448
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3549
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
50+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3651
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
3752
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
3853
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3954
import org.springframework.context.annotation.Bean;
4055
import org.springframework.core.annotation.Order;
56+
import org.springframework.core.io.Resource;
4157
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
4258
import org.springframework.transaction.PlatformTransactionManager;
59+
import org.springframework.util.Assert;
60+
import org.springframework.util.ClassUtils;
4361

4462
/**
45-
* {@link EnableAutoConfiguration Auto-configuration} for JOOQ.
63+
* {@link EnableAutoConfiguration Auto-configuration} for jOOQ.
4664
*
4765
* @author Andreas Ahlenstorf
4866
* @author Michael Simons
4967
* @author Dmytro Nosan
68+
* @author Moritz Halbritter
5069
* @since 1.3.0
5170
*/
5271
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class })
@@ -89,17 +108,57 @@ public DefaultDSLContext dslContext(org.jooq.Configuration configuration) {
89108

90109
@Bean
91110
@ConditionalOnMissingBean(org.jooq.Configuration.class)
92-
public DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider,
111+
DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider,
93112
DataSource dataSource, ObjectProvider<TransactionProvider> transactionProvider,
94113
ObjectProvider<ExecuteListenerProvider> executeListenerProviders,
95-
ObjectProvider<DefaultConfigurationCustomizer> configurationCustomizers) {
114+
ObjectProvider<DefaultConfigurationCustomizer> configurationCustomizers,
115+
ObjectProvider<Settings> settingsProvider) {
96116
DefaultConfiguration configuration = new DefaultConfiguration();
97117
configuration.set(properties.determineSqlDialect(dataSource));
98118
configuration.set(connectionProvider);
99119
transactionProvider.ifAvailable(configuration::set);
120+
settingsProvider.ifAvailable(configuration::set);
100121
configuration.set(executeListenerProviders.orderedStream().toArray(ExecuteListenerProvider[]::new));
101122
configurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
102123
return configuration;
103124
}
104125

126+
@Bean
127+
@ConditionalOnProperty("spring.jooq.config")
128+
@ConditionalOnMissingBean(Settings.class)
129+
Settings settings(JooqProperties properties) throws IOException {
130+
if (!ClassUtils.isPresent("jakarta.xml.bind.JAXBContext", null)) {
131+
throw new JaxbNotAvailableException();
132+
}
133+
Resource resource = properties.getConfig();
134+
Assert.state(resource.exists(),
135+
() -> "Resource %s set in spring.jooq.config does not exist".formatted(resource));
136+
try (InputStream stream = resource.getInputStream()) {
137+
return new JaxbSettingsLoader().load(stream);
138+
}
139+
}
140+
141+
private static final class JaxbSettingsLoader {
142+
143+
private Settings load(InputStream inputStream) {
144+
try {
145+
// See
146+
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#jaxb-unmarshaller
147+
SAXParserFactory spf = SAXParserFactory.newInstance();
148+
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
149+
spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
150+
spf.setNamespaceAware(true);
151+
spf.setXIncludeAware(false);
152+
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(inputStream));
153+
JAXBContext jc = JAXBContext.newInstance(Settings.class);
154+
Unmarshaller um = jc.createUnmarshaller();
155+
return um.unmarshal(xmlSource, Settings.class).getValue();
156+
}
157+
catch (ParserConfigurationException | JAXBException | SAXException ex) {
158+
throw new IllegalStateException("Failed to unmarshal settings", ex);
159+
}
160+
}
161+
162+
}
163+
105164
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
import org.jooq.SQLDialect;
2222

2323
import org.springframework.boot.context.properties.ConfigurationProperties;
24+
import org.springframework.core.io.Resource;
2425

2526
/**
26-
* Configuration properties for the JOOQ database library.
27+
* Configuration properties for the jOOQ database library.
2728
*
2829
* @author Andreas Ahlenstorf
2930
* @author Michael Simons
31+
* @author Moritz Halbritter
3032
* @since 1.3.0
3133
*/
3234
@ConfigurationProperties("spring.jooq")
@@ -37,6 +39,11 @@ public class JooqProperties {
3739
*/
3840
private SQLDialect sqlDialect;
3941

42+
/**
43+
* Location of the jOOQ config file.
44+
*/
45+
private Resource config;
46+
4047
public SQLDialect getSqlDialect() {
4148
return this.sqlDialect;
4249
}
@@ -45,6 +52,14 @@ public void setSqlDialect(SQLDialect sqlDialect) {
4552
this.sqlDialect = sqlDialect;
4653
}
4754

55+
public Resource getConfig() {
56+
return this.config;
57+
}
58+
59+
public void setConfig(Resource config) {
60+
this.config = config;
61+
}
62+
4863
/**
4964
* Determine the {@link SQLDialect} to use based on this configuration and the primary
5065
* {@link DataSource}.

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransaction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
2121
import org.springframework.transaction.TransactionStatus;
2222

2323
/**
24-
* Adapts a Spring transaction for JOOQ.
24+
* Adapts a Spring transaction for jOOQ.
2525
*
2626
* @author Lukas Eder
2727
* @author Andreas Ahlenstorf

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransactionProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@
2525
import org.springframework.transaction.support.DefaultTransactionDefinition;
2626

2727
/**
28-
* Allows Spring Transaction to be used with JOOQ.
28+
* Allows Spring Transaction to be used with jOOQ.
2929
*
3030
* @author Lukas Eder
3131
* @author Andreas Ahlenstorf

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
*/
1616

1717
/**
18-
* Auto-configuration for JOOQ.
18+
* Auto-configuration for jOOQ.
1919
*/
2020
package org.springframework.boot.autoconfigure.jooq;

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,
2727
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
2828
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
2929
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
30+
org.springframework.boot.autoconfigure.jooq.JaxbNotAvailableExceptionFailureAnalyzer,\
3031
org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
3132
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
3233
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
import org.jooq.TransactionContext;
2929
import org.jooq.TransactionProvider;
3030
import org.jooq.TransactionalRunnable;
31+
import org.jooq.conf.Settings;
3132
import org.jooq.impl.DataSourceConnectionProvider;
3233
import org.jooq.impl.DefaultDSLContext;
3334
import org.jooq.impl.DefaultExecuteListenerProvider;
3435
import org.junit.jupiter.api.Test;
3536

3637
import org.springframework.boot.autoconfigure.AutoConfigurations;
3738
import org.springframework.boot.jdbc.DataSourceBuilder;
39+
import org.springframework.boot.test.context.FilteredClassLoader;
3840
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3941
import org.springframework.context.annotation.Bean;
4042
import org.springframework.context.annotation.Configuration;
@@ -57,6 +59,7 @@
5759
* @author Stephane Nicoll
5860
* @author Dmytro Nosan
5961
* @author Dennis Melzer
62+
* @author Moritz Halbritter
6063
*/
6164
class JooqAutoConfigurationTests {
6265

@@ -221,6 +224,37 @@ void autoConfiguredJooqConfigurationCanBeUsedToCreateCustomDslContext() {
221224
.run((context) -> assertThat(context).hasSingleBean(DSLContext.class).hasBean("customDslContext"));
222225
}
223226

227+
@Test
228+
void shouldLoadSettingsFromConfigPropertyThroughJaxb() {
229+
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
230+
.withPropertyValues("spring.jooq.config=classpath:org/springframework/boot/autoconfigure/jooq/settings.xml")
231+
.run((context) -> {
232+
assertThat(context).hasSingleBean(Settings.class);
233+
Settings settings = context.getBean(Settings.class);
234+
assertThat(settings.getBatchSize()).isEqualTo(100);
235+
});
236+
}
237+
238+
@Test
239+
void shouldNotProvideSettingsIfJaxbIsMissing() {
240+
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
241+
.withClassLoader(new FilteredClassLoader("jakarta.xml.bind"))
242+
.withPropertyValues("spring.jooq.config=classpath:org/springframework/boot/autoconfigure/jooq/settings.xml")
243+
.run((context) -> assertThat(context).hasFailed()
244+
.getFailure()
245+
.hasRootCauseInstanceOf(JaxbNotAvailableException.class));
246+
}
247+
248+
@Test
249+
void shouldFailWithSensibleErrorMessageIfConfigIsNotFound() {
250+
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
251+
.withPropertyValues("spring.jooq.config=classpath:does-not-exist.xml")
252+
.run((context) -> assertThat(context).hasFailed()
253+
.getFailure()
254+
.hasMessageContaining("spring.jooq.config")
255+
.hasMessageContaining("does-not-exist.xml"));
256+
}
257+
224258
static class AssertFetch implements TransactionalRunnable {
225259

226260
private final DSLContext dsl;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.19.0.xsd">
3+
<batchSize>100</batchSize>
4+
</settings>

0 commit comments

Comments
 (0)