Skip to content

Commit 1e9ad93

Browse files
committed
Introduce JdbcConfiguration and extract factory methods used from AbstractJdbcConfiguration.
1 parent 42823f5 commit 1e9ad93

File tree

9 files changed

+203
-91
lines changed

9 files changed

+203
-91
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java

Lines changed: 11 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -15,51 +15,37 @@
1515
*/
1616
package org.springframework.data.jdbc.repository.config;
1717

18-
import java.util.ArrayList;
1918
import java.util.Collection;
2019
import java.util.Collections;
2120
import java.util.HashSet;
2221
import java.util.List;
2322
import java.util.Optional;
2423
import java.util.Set;
2524

26-
import org.apache.commons.logging.Log;
27-
import org.apache.commons.logging.LogFactory;
2825
import org.springframework.beans.BeansException;
2926
import org.springframework.context.ApplicationContext;
3027
import org.springframework.context.ApplicationContextAware;
3128
import org.springframework.context.annotation.Bean;
3229
import org.springframework.context.annotation.Configuration;
3330
import org.springframework.context.annotation.Lazy;
3431
import org.springframework.core.convert.converter.Converter;
35-
import org.springframework.data.convert.CustomConversions;
3632
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
3733
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
3834
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
39-
import org.springframework.data.jdbc.core.convert.DataAccessStrategyFactory;
40-
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
4135
import org.springframework.data.jdbc.core.convert.IdGeneratingEntityCallback;
4236
import org.springframework.data.jdbc.core.convert.JdbcConverter;
4337
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
44-
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
4538
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
4639
import org.springframework.data.jdbc.core.convert.RelationResolver;
4740
import org.springframework.data.jdbc.core.dialect.DialectResolver;
48-
import org.springframework.data.jdbc.core.dialect.JdbcArrayColumns;
4941
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
5042
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
51-
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
52-
import org.springframework.data.mapping.model.SimpleTypeHolder;
5343
import org.springframework.data.relational.RelationalManagedTypes;
5444
import org.springframework.data.relational.core.conversion.RelationalConverter;
5545
import org.springframework.data.relational.core.dialect.Dialect;
56-
import org.springframework.data.relational.core.mapping.DefaultNamingStrategy;
5746
import org.springframework.data.relational.core.mapping.NamingStrategy;
5847
import org.springframework.data.relational.core.mapping.Table;
59-
import org.springframework.data.util.TypeScanner;
60-
import org.springframework.jdbc.core.JdbcTemplate;
6148
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
62-
import org.springframework.util.StringUtils;
6349

6450
/**
6551
* Beans that must be registered for Spring Data JDBC to work.
@@ -77,8 +63,6 @@
7763
@Configuration(proxyBeanMethods = false)
7864
public class AbstractJdbcConfiguration implements ApplicationContextAware {
7965

80-
private static final Log LOG = LogFactory.getLog(AbstractJdbcConfiguration.class);
81-
8266
@SuppressWarnings("NullAway.Init") private ApplicationContext applicationContext;
8367

8468
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
@@ -96,7 +80,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
9680
protected Collection<String> getMappingBasePackages() {
9781

9882
Package mappingBasePackage = getClass().getPackage();
99-
return Collections.singleton(mappingBasePackage == null ? null : mappingBasePackage.getName());
83+
return mappingBasePackage == null ? List.of() : List.of(mappingBasePackage.getName());
10084
}
10185

10286
/**
@@ -124,13 +108,7 @@ public RelationalManagedTypes jdbcManagedTypes() throws ClassNotFoundException {
124108
@Bean
125109
public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy,
126110
JdbcCustomConversions customConversions, RelationalManagedTypes jdbcManagedTypes) {
127-
128-
JdbcMappingContext mappingContext = JdbcMappingContext
129-
.forQuotedIdentifiers(namingStrategy.orElse(DefaultNamingStrategy.INSTANCE));
130-
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
131-
mappingContext.setManagedTypes(jdbcManagedTypes);
132-
133-
return mappingContext;
111+
return JdbcConfiguration.createMappingContext(jdbcManagedTypes, customConversions, namingStrategy.orElse(null));
134112
}
135113

136114
/**
@@ -143,7 +121,7 @@ public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStra
143121
*/
144122
@Bean
145123
public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback(JdbcMappingContext mappingContext,
146-
NamedParameterJdbcOperations operations, Dialect dialect) {
124+
NamedParameterJdbcOperations operations, JdbcDialect dialect) {
147125
return new IdGeneratingEntityCallback(mappingContext, dialect, operations);
148126
}
149127

@@ -157,19 +135,8 @@ public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback(JdbcMappingCont
157135
*/
158136
@Bean
159137
public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParameterJdbcOperations operations,
160-
@Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, Dialect dialect) {
161-
162-
JdbcArrayColumns arrayColumns = JdbcDialect.getArraySupport(dialect);
163-
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(), arrayColumns);
164-
165-
MappingJdbcConverter mappingJdbcConverter = new MappingJdbcConverter(mappingContext, relationResolver, conversions,
166-
jdbcTypeFactory);
167-
168-
if (operations.getJdbcOperations() instanceof JdbcTemplate jdbcTemplate) {
169-
mappingJdbcConverter.setExceptionTranslator(jdbcTemplate.getExceptionTranslator());
170-
}
171-
172-
return mappingJdbcConverter;
138+
@Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, JdbcDialect dialect) {
139+
return JdbcConfiguration.createConverter(mappingContext, operations, relationResolver, conversions, dialect);
173140
}
174141

175142
/**
@@ -183,31 +150,14 @@ public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParam
183150
@Bean
184151
public JdbcCustomConversions jdbcCustomConversions() {
185152

186-
Dialect dialect = applicationContext.getBeanProvider(Dialect.class).getIfAvailable();
187-
188-
if (dialect == null) {
189-
LOG.warn("No JdbcDialect bean found; CustomConversions will be configured without dialect-specific types.");
190-
return new JdbcCustomConversions();
191-
}
192-
193-
SimpleTypeHolder simpleTypeHolder = new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER);
194-
195-
return new JdbcCustomConversions(CustomConversions.StoreConversions.of(simpleTypeHolder, storeConverters(dialect)),
196-
userConverters());
153+
JdbcDialect dialect = applicationContext.getBean(JdbcDialect.class);
154+
return JdbcConfiguration.createCustomConversions(dialect, userConverters());
197155
}
198156

199157
protected List<?> userConverters() {
200158
return Collections.emptyList();
201159
}
202160

203-
private List<Object> storeConverters(Dialect dialect) {
204-
205-
List<Object> converters = new ArrayList<>();
206-
converters.addAll(dialect.getConverters());
207-
converters.addAll(JdbcCustomConversions.storeConverters());
208-
return converters;
209-
}
210-
211161
/**
212162
* Register a {@link JdbcAggregateTemplate} as a bean for easy use in applications that need a lower level of
213163
* abstraction than the normal repository abstraction.
@@ -232,8 +182,8 @@ public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationContext applicatio
232182
*/
233183
@Bean
234184
public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations operations, JdbcConverter jdbcConverter,
235-
JdbcMappingContext context, Dialect dialect) {
236-
return new DataAccessStrategyFactory(jdbcConverter, operations, dialect, this.queryMappingConfiguration).create();
185+
JdbcMappingContext context, JdbcDialect dialect) {
186+
return JdbcConfiguration.createDataAccessStrategy(operations, jdbcConverter, queryMappingConfiguration, dialect);
237187
}
238188

239189
/**
@@ -245,7 +195,7 @@ public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations op
245195
* @throws DialectResolver.NoDialectException if the {@link Dialect} cannot be determined.
246196
*/
247197
@Bean
248-
public Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
198+
public JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
249199
return DialectResolver.getDialect(operations.getJdbcOperations());
250200
}
251201

@@ -286,16 +236,7 @@ protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
286236
* @return a set of classes identified as entities.
287237
* @since 3.0
288238
*/
289-
@SuppressWarnings("unchecked")
290239
protected Set<Class<?>> scanForEntities(String basePackage) {
291-
292-
if (!StringUtils.hasText(basePackage)) {
293-
return Collections.emptySet();
294-
}
295-
296-
return TypeScanner.typeScanner(AbstractJdbcConfiguration.class.getClassLoader()) //
297-
.forTypesAnnotatedWith(Table.class) //
298-
.scanPackages(basePackage) //
299-
.collectAsSet();
240+
return JdbcConfiguration.scanForEntities(basePackage);
300241
}
301242
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright 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+
package org.springframework.data.jdbc.repository.config;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Set;
22+
23+
import org.jspecify.annotations.Nullable;
24+
25+
import org.springframework.core.convert.converter.Converter;
26+
import org.springframework.data.convert.CustomConversions;
27+
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
28+
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
29+
import org.springframework.data.jdbc.core.convert.DataAccessStrategyFactory;
30+
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
31+
import org.springframework.data.jdbc.core.convert.JdbcConverter;
32+
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
33+
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
34+
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
35+
import org.springframework.data.jdbc.core.convert.RelationResolver;
36+
import org.springframework.data.jdbc.core.dialect.JdbcArrayColumns;
37+
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
38+
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
39+
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
40+
import org.springframework.data.mapping.model.SimpleTypeHolder;
41+
import org.springframework.data.relational.RelationalManagedTypes;
42+
import org.springframework.data.relational.core.dialect.Dialect;
43+
import org.springframework.data.relational.core.mapping.DefaultNamingStrategy;
44+
import org.springframework.data.relational.core.mapping.NamingStrategy;
45+
import org.springframework.data.relational.core.mapping.Table;
46+
import org.springframework.data.util.TypeScanner;
47+
import org.springframework.jdbc.core.JdbcTemplate;
48+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
49+
import org.springframework.util.StringUtils;
50+
51+
/**
52+
* Utility class to providing factory methods for JDBC infrastructure components.
53+
* <p>
54+
* Mainly for use within the framework or for configuration arrangements that require customization of configuration.
55+
*
56+
* @author Mark Paluch
57+
* @since 4.0
58+
*/
59+
public final class JdbcConfiguration {
60+
61+
private JdbcConfiguration() {}
62+
63+
/**
64+
* Register a {@link JdbcMappingContext} and apply an optional {@link NamingStrategy}.
65+
*
66+
* @param jdbcManagedTypes JDBC managed types.
67+
* @param customConversions the custom conversions.
68+
* @param namingStrategy optional {@link NamingStrategy}. Use {@link DefaultNamingStrategy#INSTANCE} as fallback.
69+
* @return must not be {@literal null}.
70+
*/
71+
public static JdbcMappingContext createMappingContext(RelationalManagedTypes jdbcManagedTypes,
72+
JdbcCustomConversions customConversions, @Nullable NamingStrategy namingStrategy) {
73+
74+
JdbcMappingContext mappingContext = JdbcMappingContext
75+
.forQuotedIdentifiers(namingStrategy != null ? namingStrategy : DefaultNamingStrategy.INSTANCE);
76+
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
77+
mappingContext.setManagedTypes(jdbcManagedTypes);
78+
79+
return mappingContext;
80+
}
81+
82+
/**
83+
* Creates a {@link JdbcConverter}.
84+
*
85+
* @param mappingContext must not be {@literal null}.
86+
* @param operations must not be {@literal null}.
87+
* @param relationResolver must not be {@literal null}.
88+
* @param conversions must not be {@literal null}.
89+
* @param dialect the JDBC dialect in use.
90+
* @return must not be {@literal null}.
91+
*/
92+
public static JdbcConverter createConverter(JdbcMappingContext mappingContext,
93+
NamedParameterJdbcOperations operations, RelationResolver relationResolver, JdbcCustomConversions conversions,
94+
JdbcDialect dialect) {
95+
96+
JdbcArrayColumns arrayColumns = JdbcDialect.getArraySupport(dialect);
97+
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(), arrayColumns);
98+
99+
MappingJdbcConverter mappingJdbcConverter = new MappingJdbcConverter(mappingContext, relationResolver, conversions,
100+
jdbcTypeFactory);
101+
102+
if (operations.getJdbcOperations() instanceof JdbcTemplate jdbcTemplate) {
103+
mappingJdbcConverter.setExceptionTranslator(jdbcTemplate.getExceptionTranslator());
104+
}
105+
106+
return mappingJdbcConverter;
107+
}
108+
109+
/**
110+
* Register custom {@link Converter}s in a {@link JdbcCustomConversions} object if required.
111+
*
112+
* @param dialect the JDBC dialect in use.
113+
* @param userConverters list of user converters, must not be {@literal null}.
114+
* @return will never be {@literal null}.
115+
*/
116+
public static JdbcCustomConversions createCustomConversions(JdbcDialect dialect, List<?> userConverters) {
117+
118+
SimpleTypeHolder simpleTypeHolder = new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER);
119+
120+
return new JdbcCustomConversions(CustomConversions.StoreConversions.of(simpleTypeHolder, storeConverters(dialect)),
121+
userConverters);
122+
}
123+
124+
private static List<Object> storeConverters(Dialect dialect) {
125+
126+
List<Object> converters = new ArrayList<>();
127+
converters.addAll(dialect.getConverters());
128+
converters.addAll(JdbcCustomConversions.storeConverters());
129+
return converters;
130+
}
131+
132+
/**
133+
* Create a {@link DataAccessStrategy} for reuse in the {@link JdbcAggregateOperations} and the {@link JdbcConverter}.
134+
* Override this method to register a bean of type {@link DataAccessStrategy} if your use case requires a more
135+
* specialized {@link DataAccessStrategy}.
136+
*
137+
* @param operations must not be {@literal null}.
138+
* @param jdbcConverter must not be {@literal null}.
139+
* @param mappingConfiguration mapping configuration, can be {@literal null}.
140+
* @param dialect the JDBC dialect in use.
141+
* @return will never be {@literal null}.
142+
*/
143+
public static DataAccessStrategy createDataAccessStrategy(NamedParameterJdbcOperations operations,
144+
JdbcConverter jdbcConverter, @Nullable QueryMappingConfiguration mappingConfiguration, JdbcDialect dialect) {
145+
return new DataAccessStrategyFactory(jdbcConverter, operations, dialect,
146+
mappingConfiguration == null ? QueryMappingConfiguration.EMPTY : mappingConfiguration).create();
147+
}
148+
149+
/**
150+
* Scans the given base package for entities, i.e. JDBC-specific types annotated with {@link Table}.
151+
*
152+
* @param basePackage must not be {@literal null}.
153+
* @return a set of classes identified as entities.
154+
* @since 3.0
155+
*/
156+
@SuppressWarnings("unchecked")
157+
public static Set<Class<?>> scanForEntities(String basePackage) {
158+
159+
if (!StringUtils.hasText(basePackage)) {
160+
return Collections.emptySet();
161+
}
162+
163+
return TypeScanner.typeScanner(JdbcConfiguration.class.getClassLoader()) //
164+
.forTypesAnnotatedWith(Table.class) //
165+
.scanPackages(basePackage) //
166+
.collectAsSet();
167+
}
168+
169+
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,16 @@
1818
import java.util.Optional;
1919

2020
import org.apache.ibatis.session.SqlSession;
21+
2122
import org.springframework.beans.factory.annotation.Autowired;
2223
import org.springframework.context.annotation.Bean;
2324
import org.springframework.context.annotation.Configuration;
2425
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
2526
import org.springframework.data.jdbc.core.convert.JdbcConverter;
2627
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
28+
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
2729
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
2830
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
29-
import org.springframework.data.relational.core.dialect.Dialect;
3031
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
3132

3233
/**
@@ -46,7 +47,7 @@ public class MyBatisJdbcConfiguration extends AbstractJdbcConfiguration {
4647
@Bean
4748
@Override
4849
public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations operations, JdbcConverter jdbcConverter,
49-
JdbcMappingContext context, Dialect dialect) {
50+
JdbcMappingContext context, JdbcDialect dialect) {
5051

5152
return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, jdbcConverter, operations, session, dialect,
5253
queryMappingConfiguration.orElse(QueryMappingConfiguration.EMPTY));

0 commit comments

Comments
 (0)