Skip to content

Commit becc753

Browse files
mp911deschauder
authored andcommitted
Polishing.
Deprecate original DialectResolver and JdbcArrayColumns as they've been in the wrong package and introduce replacements in the dialect package. Let deprecated types extend from their replacements to retain compatibility. Make instance holders final, fix Javadoc typos, update reference docs. Original pull request #2036 See #2031
1 parent 7698d48 commit becc753

File tree

22 files changed

+489
-141
lines changed

22 files changed

+489
-141
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java

+22-2
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,27 @@
3333
public class DefaultJdbcTypeFactory implements JdbcTypeFactory {
3434

3535
private final JdbcOperations operations;
36-
private final JdbcArrayColumns arrayColumns;
36+
private final org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns;
3737

3838
/**
3939
* Creates a new {@link DefaultJdbcTypeFactory}.
4040
*
4141
* @param operations must not be {@literal null}.
4242
*/
4343
public DefaultJdbcTypeFactory(JdbcOperations operations) {
44-
this(operations, JdbcArrayColumns.DefaultSupport.INSTANCE);
44+
this(operations, org.springframework.data.jdbc.core.dialect.JdbcArrayColumns.DefaultSupport.INSTANCE);
4545
}
4646

4747
/**
4848
* Creates a new {@link DefaultJdbcTypeFactory}.
4949
*
5050
* @param operations must not be {@literal null}.
5151
* @since 2.3
52+
* @deprecated use
53+
* {@link #DefaultJdbcTypeFactory(JdbcOperations, org.springframework.data.jdbc.core.dialect.JdbcArrayColumns)}
54+
* instead.
5255
*/
56+
@Deprecated(forRemoval = true, since = "3.5")
5357
public DefaultJdbcTypeFactory(JdbcOperations operations, JdbcArrayColumns arrayColumns) {
5458

5559
Assert.notNull(operations, "JdbcOperations must not be null");
@@ -59,6 +63,22 @@ public DefaultJdbcTypeFactory(JdbcOperations operations, JdbcArrayColumns arrayC
5963
this.arrayColumns = arrayColumns;
6064
}
6165

66+
/**
67+
* Creates a new {@link DefaultJdbcTypeFactory}.
68+
*
69+
* @param operations must not be {@literal null}.
70+
* @since 3.5
71+
*/
72+
public DefaultJdbcTypeFactory(JdbcOperations operations,
73+
org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns) {
74+
75+
Assert.notNull(operations, "JdbcOperations must not be null");
76+
Assert.notNull(arrayColumns, "JdbcArrayColumns must not be null");
77+
78+
this.operations = operations;
79+
this.arrayColumns = arrayColumns;
80+
}
81+
6282
@Override
6383
public Array createArray(Object[] value) {
6484

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcArrayColumns.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
* @author Jens Schauder
2727
* @author Mark Paluch
2828
* @since 2.3
29+
* @deprecated since 3.5, replacement moved to {@link org.springframework.data.jdbc.core.dialect.JdbcArrayColumns}.
2930
*/
30-
public interface JdbcArrayColumns extends ArrayColumns {
31+
@Deprecated(forRemoval = true)
32+
public interface JdbcArrayColumns extends org.springframework.data.jdbc.core.dialect.JdbcArrayColumns {
3133

3234
@Override
3335
default Class<?> getArrayType(Class<?> userType) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
* Copyright 2020-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.core.dialect;
17+
18+
import java.sql.Connection;
19+
import java.sql.DatabaseMetaData;
20+
import java.sql.SQLException;
21+
import java.util.Collection;
22+
import java.util.List;
23+
import java.util.Locale;
24+
import java.util.Optional;
25+
import java.util.Set;
26+
import java.util.stream.Stream;
27+
28+
import javax.sql.DataSource;
29+
30+
import org.apache.commons.logging.Log;
31+
import org.apache.commons.logging.LogFactory;
32+
import org.springframework.core.io.support.SpringFactoriesLoader;
33+
import org.springframework.dao.NonTransientDataAccessException;
34+
import org.springframework.data.relational.core.dialect.Dialect;
35+
import org.springframework.data.relational.core.dialect.Escaper;
36+
import org.springframework.data.relational.core.dialect.IdGeneration;
37+
import org.springframework.data.relational.core.dialect.InsertRenderContext;
38+
import org.springframework.data.relational.core.dialect.LimitClause;
39+
import org.springframework.data.relational.core.dialect.LockClause;
40+
import org.springframework.data.relational.core.dialect.OrderByNullPrecedence;
41+
import org.springframework.data.relational.core.sql.IdentifierProcessing;
42+
import org.springframework.data.relational.core.sql.SimpleFunction;
43+
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
44+
import org.springframework.data.util.Optionals;
45+
import org.springframework.jdbc.core.ConnectionCallback;
46+
import org.springframework.jdbc.core.JdbcOperations;
47+
import org.springframework.lang.Nullable;
48+
import org.springframework.util.StringUtils;
49+
50+
/**
51+
* Resolves a {@link Dialect}. Resolution typically uses {@link JdbcOperations} to obtain and inspect a
52+
* {@link Connection}. Dialect resolution uses Spring's {@link SpringFactoriesLoader spring.factories} to determine
53+
* available {@link JdbcDialectProvider extensions}.
54+
*
55+
* @author Jens Schauder
56+
* @author Mikhail Polivakha
57+
* @since 3.5
58+
* @see Dialect
59+
* @see SpringFactoriesLoader
60+
*/
61+
public class DialectResolver {
62+
63+
private static final Log LOG = LogFactory.getLog(DialectResolver.class);
64+
65+
private static final List<JdbcDialectProvider> DETECTORS = SpringFactoriesLoader
66+
.loadFactories(JdbcDialectProvider.class, DialectResolver.class.getClassLoader());
67+
68+
private static final List<org.springframework.data.jdbc.repository.config.DialectResolver.JdbcDialectProvider> LEGACY_DETECTORS = SpringFactoriesLoader
69+
.loadFactories(org.springframework.data.jdbc.repository.config.DialectResolver.JdbcDialectProvider.class,
70+
DialectResolver.class.getClassLoader());
71+
72+
// utility constructor.
73+
private DialectResolver() {}
74+
75+
/**
76+
* Retrieve a {@link Dialect} by inspecting a {@link Connection}.
77+
*
78+
* @param operations must not be {@literal null}.
79+
* @return the resolved {@link Dialect} {@link NoDialectException} if the database type cannot be determined from
80+
* {@link DataSource}.
81+
* @throws NoDialectException if no {@link Dialect} can be found.
82+
*/
83+
public static JdbcDialect getDialect(JdbcOperations operations) {
84+
85+
return Stream.concat(LEGACY_DETECTORS.stream(), DETECTORS.stream()) //
86+
.map(it -> it.getDialect(operations)) //
87+
.flatMap(Optionals::toStream) //
88+
.map(it -> it instanceof JdbcDialect ? (JdbcDialect) it : new JdbcDialectAdapter(it)).findFirst() //
89+
.orElseThrow(() -> new NoDialectException(
90+
String.format("Cannot determine a dialect for %s; Please provide a Dialect", operations)));
91+
}
92+
93+
/**
94+
* SPI to extend Spring's default JDBC Dialect discovery mechanism. Implementations of this interface are discovered
95+
* through Spring's {@link SpringFactoriesLoader} mechanism.
96+
*
97+
* @author Jens Schauder
98+
* @see SpringFactoriesLoader
99+
*/
100+
public interface JdbcDialectProvider {
101+
102+
/**
103+
* Returns a {@link Dialect} for a {@link DataSource}.
104+
*
105+
* @param operations the {@link JdbcOperations} to be used with the {@link Dialect}.
106+
* @return {@link Optional} containing the {@link Dialect} if the {@link JdbcDialectProvider} can provide a dialect
107+
* object, otherwise {@link Optional#empty()}.
108+
*/
109+
Optional<Dialect> getDialect(JdbcOperations operations);
110+
}
111+
112+
public static class DefaultDialectProvider implements JdbcDialectProvider {
113+
114+
@Override
115+
public Optional<Dialect> getDialect(JdbcOperations operations) {
116+
return Optional.ofNullable(operations.execute((ConnectionCallback<Dialect>) DefaultDialectProvider::getDialect));
117+
}
118+
119+
@Nullable
120+
private static JdbcDialect getDialect(Connection connection) throws SQLException {
121+
122+
DatabaseMetaData metaData = connection.getMetaData();
123+
124+
String name = metaData.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
125+
126+
if (name.contains("hsql")) {
127+
return JdbcHsqlDbDialect.INSTANCE;
128+
}
129+
if (name.contains("h2")) {
130+
return JdbcH2Dialect.INSTANCE;
131+
}
132+
if (name.contains("mysql")) {
133+
return new JdbcMySqlDialect(getIdentifierProcessing(metaData));
134+
}
135+
if (name.contains("mariadb")) {
136+
return new JdbcMariaDbDialect(getIdentifierProcessing(metaData));
137+
}
138+
if (name.contains("postgresql")) {
139+
return JdbcPostgresDialect.INSTANCE;
140+
}
141+
if (name.contains("microsoft")) {
142+
return JdbcSqlServerDialect.INSTANCE;
143+
}
144+
if (name.contains("db2")) {
145+
return JdbcDb2Dialect.INSTANCE;
146+
}
147+
if (name.contains("oracle")) {
148+
return JdbcOracleDialect.INSTANCE;
149+
}
150+
151+
LOG.info(String.format("Couldn't determine Dialect for \"%s\"", name));
152+
return null;
153+
}
154+
155+
private static IdentifierProcessing getIdentifierProcessing(DatabaseMetaData metaData) throws SQLException {
156+
157+
// getIdentifierQuoteString() returns a space " " if identifier quoting is not
158+
// supported.
159+
String quoteString = metaData.getIdentifierQuoteString();
160+
IdentifierProcessing.Quoting quoting = StringUtils.hasText(quoteString)
161+
? new IdentifierProcessing.Quoting(quoteString)
162+
: IdentifierProcessing.Quoting.NONE;
163+
164+
IdentifierProcessing.LetterCasing letterCasing;
165+
// IdentifierProcessing tries to mimic the behavior of unquoted identifiers for their quoted variants.
166+
if (metaData.supportsMixedCaseIdentifiers()) {
167+
letterCasing = IdentifierProcessing.LetterCasing.AS_IS;
168+
} else if (metaData.storesUpperCaseIdentifiers()) {
169+
letterCasing = IdentifierProcessing.LetterCasing.UPPER_CASE;
170+
} else if (metaData.storesLowerCaseIdentifiers()) {
171+
letterCasing = IdentifierProcessing.LetterCasing.LOWER_CASE;
172+
} else { // this shouldn't happen since one of the previous cases should be true.
173+
// But if it does happen, we go with the ANSI default.
174+
letterCasing = IdentifierProcessing.LetterCasing.UPPER_CASE;
175+
}
176+
177+
return IdentifierProcessing.create(quoting, letterCasing);
178+
}
179+
}
180+
181+
/**
182+
* Exception thrown when {@link DialectResolver} cannot resolve a {@link Dialect}.
183+
*/
184+
public static class NoDialectException extends NonTransientDataAccessException {
185+
186+
/**
187+
* Constructor for NoDialectFoundException.
188+
*
189+
* @param msg the detail message
190+
*/
191+
protected NoDialectException(String msg) {
192+
super(msg);
193+
}
194+
}
195+
196+
private static class JdbcDialectAdapter implements JdbcDialect {
197+
198+
private final Dialect delegate;
199+
private final JdbcArrayColumnsAdapter arrayColumns;
200+
201+
public JdbcDialectAdapter(Dialect delegate) {
202+
this.delegate = delegate;
203+
this.arrayColumns = new JdbcArrayColumnsAdapter(delegate.getArraySupport());
204+
}
205+
206+
@Override
207+
public LimitClause limit() {
208+
return delegate.limit();
209+
}
210+
211+
@Override
212+
public LockClause lock() {
213+
return delegate.lock();
214+
}
215+
216+
@Override
217+
public JdbcArrayColumns getArraySupport() {
218+
return arrayColumns;
219+
}
220+
221+
@Override
222+
public SelectRenderContext getSelectContext() {
223+
return delegate.getSelectContext();
224+
}
225+
226+
@Override
227+
public IdentifierProcessing getIdentifierProcessing() {
228+
return delegate.getIdentifierProcessing();
229+
}
230+
231+
@Override
232+
public Escaper getLikeEscaper() {
233+
return delegate.getLikeEscaper();
234+
}
235+
236+
@Override
237+
public IdGeneration getIdGeneration() {
238+
return delegate.getIdGeneration();
239+
}
240+
241+
@Override
242+
public Collection<Object> getConverters() {
243+
return delegate.getConverters();
244+
}
245+
246+
@Override
247+
public Set<Class<?>> simpleTypes() {
248+
return delegate.simpleTypes();
249+
}
250+
251+
@Override
252+
public InsertRenderContext getInsertRenderContext() {
253+
return delegate.getInsertRenderContext();
254+
}
255+
256+
@Override
257+
public OrderByNullPrecedence orderByNullHandling() {
258+
return delegate.orderByNullHandling();
259+
}
260+
261+
@Override
262+
public SimpleFunction getExistsFunction() {
263+
return delegate.getExistsFunction();
264+
}
265+
266+
@Override
267+
public boolean supportsSingleQueryLoading() {
268+
return delegate.supportsSingleQueryLoading();
269+
}
270+
}
271+
272+
}

0 commit comments

Comments
 (0)