Skip to content

Commit 28d78c4

Browse files
committed
GH-2020 Added SqlTypeResolver abstraction
Signed-off-by: mipo256 <mikhailpolivakha@gmail.com>
1 parent 38f2af0 commit 28d78c4

20 files changed

+646
-90
lines changed

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

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,6 @@ public DefaultJdbcTypeFactory(JdbcOperations operations) {
4444
this(operations, org.springframework.data.jdbc.core.dialect.JdbcArrayColumns.DefaultSupport.INSTANCE);
4545
}
4646

47-
/**
48-
* Creates a new {@link DefaultJdbcTypeFactory}.
49-
*
50-
* @param operations must not be {@literal null}.
51-
* @since 2.3
52-
* @deprecated use
53-
* {@link #DefaultJdbcTypeFactory(JdbcOperations, org.springframework.data.jdbc.core.dialect.JdbcArrayColumns)}
54-
* instead.
55-
*/
56-
@Deprecated(forRemoval = true, since = "3.5")
57-
public DefaultJdbcTypeFactory(JdbcOperations operations, JdbcArrayColumns arrayColumns) {
58-
59-
Assert.notNull(operations, "JdbcOperations must not be null");
60-
Assert.notNull(arrayColumns, "JdbcArrayColumns must not be null");
61-
62-
this.operations = operations;
63-
this.arrayColumns = arrayColumns;
64-
}
65-
6647
/**
6748
* Creates a new {@link DefaultJdbcTypeFactory}.
6849
*

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
4545

4646
private DataAccessStrategy delegate;
4747

48+
/**
49+
* @deprecated please, use {@link DelegatingDataAccessStrategy#DelegatingDataAccessStrategy(DataAccessStrategy)} instead
50+
*/
51+
@Deprecated(forRemoval = true, since = "4.0")
4852
public DelegatingDataAccessStrategy() {}
4953

5054
public DelegatingDataAccessStrategy(DataAccessStrategy delegate) {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
17+
package org.springframework.data.jdbc.core.dialect;
18+
19+
import java.sql.SQLType;
20+
21+
import org.springframework.data.relational.repository.query.RelationalParameters;
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* Default implementation of {@link SqlTypeResolver}. Capable to resolve the {@link SqlType} annotation
27+
* on the {@link java.lang.annotation.ElementType#PARAMETER method parameters}, like this:
28+
* <p>
29+
* <pre class="code">
30+
* List<User> findByAge(&#64;SqlType(name = "TINYINT", vendorTypeNumber = Types.TINYINT) byte age);
31+
* </pre>
32+
*
33+
* Qualification of the actual {@link SQLType} (the sql type of the component), then the following needs to be done:
34+
* <pre class="code">
35+
* List<User> findByAgeIn(&#64;SqlType(name = "TINYINT", vendorTypeNumber = Types.TINYINT) Integer[] age);
36+
* </pre>
37+
*
38+
* @author Mikhail Polivakha
39+
*/
40+
public class DefaultSqlTypeResolver implements SqlTypeResolver {
41+
42+
public static DefaultSqlTypeResolver INSTANCE = new DefaultSqlTypeResolver();
43+
44+
@Override
45+
@Nullable
46+
public SQLType resolveSqlType(RelationalParameters.RelationalParameter relationalParameter) {
47+
return resolveInternally(relationalParameter);
48+
}
49+
50+
@Override
51+
@Nullable
52+
public SQLType resolveActualSqlType(RelationalParameters.RelationalParameter relationalParameter) {
53+
return resolveInternally(relationalParameter);
54+
}
55+
56+
private static AnnotationBasedSqlType resolveInternally(
57+
RelationalParameters.RelationalParameter relationalParameter) {
58+
SqlType parameterAnnotation = relationalParameter.getMethodParameter().getParameterAnnotation(SqlType.class);
59+
60+
if (parameterAnnotation != null) {
61+
return new AnnotationBasedSqlType(parameterAnnotation);
62+
} else {
63+
return null;
64+
}
65+
}
66+
67+
/**
68+
* {@link SQLType} determined from the {@link SqlType} annotation.
69+
*
70+
* @author Mikhail Polivakha
71+
*/
72+
protected static class AnnotationBasedSqlType implements SQLType {
73+
74+
private final SqlType sqlType;
75+
76+
public AnnotationBasedSqlType(SqlType sqlType) {
77+
Assert.notNull(sqlType, "sqlType must not be null");
78+
79+
this.sqlType = sqlType;
80+
}
81+
82+
@Override
83+
public String getName() {
84+
return sqlType.name();
85+
}
86+
87+
@Override
88+
public String getVendor() {
89+
return "Spring Data JDBC";
90+
}
91+
92+
@Override
93+
public Integer getVendorTypeNumber() {
94+
return sqlType.vendorTypeNumber();
95+
}
96+
}
97+
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package org.springframework.data.jdbc.core.dialect;
1717

1818
import org.springframework.data.relational.core.dialect.Dialect;
19+
import org.springframework.data.jdbc.core.dialect.SqlTypeResolver;
20+
import org.springframework.data.jdbc.core.dialect.DefaultSqlTypeResolver;
1921

2022
/**
2123
* {@link org.springframework.data.relational.core.dialect.ArrayColumns} that offer JDBC specific functionality.
@@ -37,4 +39,12 @@ default JdbcArrayColumns getArraySupport() {
3739
return JdbcArrayColumns.Unsupported.INSTANCE;
3840
}
3941

42+
/**
43+
* Returns a {@link SqlTypeResolver} of this dialect.
44+
*
45+
* @since 4.0
46+
*/
47+
default SqlTypeResolver getSqlTypeResolver() {
48+
return DefaultSqlTypeResolver.INSTANCE;
49+
}
4050
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
17+
package org.springframework.data.jdbc.core.dialect;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
import java.sql.SQLType;
25+
26+
/**
27+
* Serves as a hint to the {@link DefaultSqlTypeResolver}, that signals the {@link java.sql.SQLType} to be used.
28+
* The arguments of this annotation are identical to the methods on {@link java.sql.SQLType} interface, expect for
29+
* the {@link SQLType#getVendor()}, which is absent, because it typically does not matter as such for the underlying
30+
* JDBC drivers. The examples of usage, can be found in javadoc of {@link DefaultSqlTypeResolver}.
31+
*
32+
* @see DefaultSqlTypeResolver
33+
* @author Mikhail Polivakha
34+
*/
35+
@Documented
36+
@Target({ElementType.PARAMETER})
37+
@Retention(RetentionPolicy.RUNTIME)
38+
public @interface SqlType {
39+
40+
/**
41+
* Returns the {@code SQLType} name that represents a SQL data type.
42+
*
43+
* @return The name of this {@code SQLType}.
44+
*/
45+
String name();
46+
47+
/**
48+
* Returns the vendor specific type number for the data type.
49+
*
50+
* @return An Integer representing the vendor specific data type
51+
*/
52+
int vendorTypeNumber();
53+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
17+
package org.springframework.data.jdbc.core.dialect;
18+
19+
import java.sql.SQLType;
20+
21+
import org.springframework.data.relational.repository.query.RelationalParameters.RelationalParameter;
22+
import org.springframework.data.util.TypeInformation;
23+
import org.springframework.lang.Nullable;
24+
25+
/**
26+
* Common interface for all objects capable to resolve the {@link SQLType} to be used for a give method parameter.
27+
*
28+
* @author Mikhail Polivakha
29+
*/
30+
public interface SqlTypeResolver {
31+
32+
/**
33+
* Resolving the {@link SQLType} from the given {@link RelationalParameter}.
34+
*
35+
* @param relationalParameter the parameter of the query method
36+
* @return {@code null} in case the given {@link SqlTypeResolver} cannot or do not want to determine the
37+
* {@link SQLType} of the given parameter
38+
*/
39+
@Nullable
40+
SQLType resolveSqlType(RelationalParameter relationalParameter);
41+
42+
/**
43+
* Resolving the {@link SQLType} from the given {@link RelationalParameter}. The definition of "actual"
44+
* type can be looked up in the {@link TypeInformation#getActualType()}.
45+
*
46+
* @param relationalParameter the parameter of the query method
47+
* @return {@code null} in case the given {@link SqlTypeResolver} cannot or do not want to determine the
48+
* actual {@link SQLType} of the given parameter
49+
*/
50+
@Nullable
51+
SQLType resolveActualSqlType(RelationalParameter relationalParameter);
52+
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,47 @@
2121
import org.springframework.core.MethodParameter;
2222
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
2323
import org.springframework.data.jdbc.support.JdbcUtil;
24+
import org.springframework.data.jdbc.core.dialect.DefaultSqlTypeResolver;
25+
import org.springframework.data.jdbc.core.dialect.SqlTypeResolver;
2426
import org.springframework.data.relational.repository.query.RelationalParameters;
2527
import org.springframework.data.repository.query.Parameter;
2628
import org.springframework.data.repository.query.ParametersSource;
2729
import org.springframework.data.util.Lazy;
2830
import org.springframework.data.util.TypeInformation;
31+
import org.springframework.util.Assert;
2932

3033
/**
3134
* Custom extension of {@link RelationalParameters}.
3235
*
3336
* @author Mark Paluch
37+
* @author Mikhail Polivakha
3438
* @since 3.2.6
3539
*/
3640
public class JdbcParameters extends RelationalParameters {
3741

3842
/**
39-
* Creates a new {@link JdbcParameters} instance from the given {@link ParametersSource}.
43+
* Creates a new {@link JdbcParameters} instance from the given {@link ParametersSource}. Uses the {@link DefaultSqlTypeResolver}.
4044
*
4145
* @param parametersSource must not be {@literal null}.
4246
*/
4347
public JdbcParameters(ParametersSource parametersSource) {
4448
super(parametersSource,
45-
methodParameter -> new JdbcParameter(methodParameter, parametersSource.getDomainTypeInformation()));
49+
methodParameter -> new JdbcParameter(methodParameter, parametersSource.getDomainTypeInformation(),
50+
DefaultSqlTypeResolver.INSTANCE));
51+
}
52+
53+
/**
54+
* Creates a new {@link JdbcParameters} instance from the given {@link ParametersSource} and given {@link SqlTypeResolver}.
55+
*
56+
* @param parametersSource must not be {@literal null}.
57+
* @param sqlTypeResolver must not be {@literal null}.
58+
*/
59+
public JdbcParameters(ParametersSource parametersSource, SqlTypeResolver sqlTypeResolver) {
60+
super(parametersSource,
61+
methodParameter -> new JdbcParameter(methodParameter, parametersSource.getDomainTypeInformation(), sqlTypeResolver));
62+
63+
Assert.notNull(sqlTypeResolver, "SqlTypeResolver must not be null");
64+
Assert.notNull(parametersSource, "ParametersSource must not be null");
4665
}
4766

4867
@SuppressWarnings({ "rawtypes", "unchecked" })
@@ -69,27 +88,42 @@ protected JdbcParameters createFrom(List<RelationalParameter> parameters) {
6988
*/
7089
public static class JdbcParameter extends RelationalParameter {
7190

72-
private final SQLType sqlType;
91+
private final Lazy<SQLType> sqlType;
7392
private final Lazy<SQLType> actualSqlType;
7493

7594
/**
7695
* Creates a new {@link RelationalParameter}.
7796
*
7897
* @param parameter must not be {@literal null}.
7998
*/
80-
JdbcParameter(MethodParameter parameter, TypeInformation<?> domainType) {
99+
JdbcParameter(MethodParameter parameter, TypeInformation<?> domainType, SqlTypeResolver sqlTypeResolver) {
81100
super(parameter, domainType);
82101

83102
TypeInformation<?> typeInformation = getTypeInformation();
84103

85-
sqlType = JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getType()));
104+
sqlType = Lazy.of(() -> {
105+
SQLType resolvedSqlType = sqlTypeResolver.resolveSqlType(this);
106+
107+
if (resolvedSqlType == null) {
108+
return JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getType()));
109+
} else {
110+
return resolvedSqlType;
111+
}
112+
});
113+
114+
actualSqlType = Lazy.of(() -> {
115+
SQLType resolvedActualSqlType = sqlTypeResolver.resolveActualSqlType(this);
86116

87-
actualSqlType = Lazy.of(() -> JdbcUtil
88-
.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getActualType().getType())));
117+
if (resolvedActualSqlType == null) {
118+
return JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getActualType().getType()));
119+
} else {
120+
return resolvedActualSqlType;
121+
}
122+
});
89123
}
90124

91125
public SQLType getSqlType() {
92-
return sqlType;
126+
return sqlType.get();
93127
}
94128

95129
public SQLType getActualSqlType() {

0 commit comments

Comments
 (0)