Skip to content

Commit d92a729

Browse files
committed
GH-2020 Added SqlTypeResolver abstraction
Signed-off-by: mipo256 <[email protected]>
1 parent f3dc789 commit d92a729

File tree

12 files changed

+488
-76
lines changed

12 files changed

+488
-76
lines changed

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

+42-8
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.relational.core.dialect.DefaultSqlTypeResolver;
25+
import org.springframework.data.relational.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+
Lazy.of(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, Lazy<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, Lazy<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.get().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.get().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() {

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,28 @@
1717

1818
import java.lang.annotation.Annotation;
1919
import java.lang.reflect.Method;
20+
import java.util.List;
2021
import java.util.Map;
2122
import java.util.Optional;
2223

2324
import org.springframework.core.annotation.AnnotatedElementUtils;
2425
import org.springframework.core.annotation.AnnotationUtils;
2526
import org.springframework.data.mapping.context.MappingContext;
2627
import org.springframework.data.projection.ProjectionFactory;
28+
import org.springframework.data.relational.core.dialect.DefaultSqlTypeResolver;
29+
import org.springframework.data.relational.core.dialect.SqlTypeResolver;
2730
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
2831
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
2932
import org.springframework.data.relational.repository.Lock;
3033
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
3134
import org.springframework.data.relational.repository.query.SimpleRelationalEntityMetadata;
3235
import org.springframework.data.repository.core.NamedQueries;
3336
import org.springframework.data.repository.core.RepositoryMetadata;
37+
import org.springframework.data.repository.query.Parameter;
3438
import org.springframework.data.repository.query.Parameters;
3539
import org.springframework.data.repository.query.ParametersSource;
3640
import org.springframework.data.repository.query.QueryMethod;
41+
import org.springframework.data.util.Lazy;
3742
import org.springframework.jdbc.core.ResultSetExtractor;
3843
import org.springframework.jdbc.core.RowMapper;
3944
import org.springframework.lang.Nullable;
@@ -52,6 +57,7 @@
5257
* @author Hebert Coelho
5358
* @author Diego Krupitza
5459
* @author Mark Paluch
60+
* @author Mikhail Polivakha
5561
*/
5662
public class JdbcQueryMethod extends QueryMethod {
5763

@@ -62,22 +68,33 @@ public class JdbcQueryMethod extends QueryMethod {
6268
private @Nullable RelationalEntityMetadata<?> metadata;
6369
private final boolean modifyingQuery;
6470

71+
private final SqlTypeResolver sqlTypeResolver;
72+
6573
// TODO: Remove NamedQueries and put it into JdbcQueryLookupStrategy
6674
public JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
6775
NamedQueries namedQueries,
6876
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext) {
77+
this(method, metadata, factory, namedQueries, mappingContext, DefaultSqlTypeResolver.INSTANCE);
78+
}
79+
80+
public JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
81+
NamedQueries namedQueries,
82+
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext,
83+
SqlTypeResolver sqlTypeResolver) {
6984

7085
super(method, metadata, factory);
7186
this.namedQueries = namedQueries;
7287
this.method = method;
7388
this.mappingContext = mappingContext;
7489
this.annotationCache = new ConcurrentReferenceHashMap<>();
7590
this.modifyingQuery = AnnotationUtils.findAnnotation(method, Modifying.class) != null;
91+
this.sqlTypeResolver = sqlTypeResolver;
7692
}
7793

94+
// SqlTypeResolver has to be wrapped, becuase the createParameters() is invoked in the parents constructor before child initialization
7895
@Override
7996
protected Parameters<?, ?> createParameters(ParametersSource parametersSource) {
80-
return new JdbcParameters(parametersSource);
97+
return new JdbcParameters(parametersSource, Lazy.of(() -> this.sqlTypeResolver));
8198
}
8299

83100
@Override

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

+1-59
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.data.jdbc.core.convert.JdbcConverter;
4040
import org.springframework.data.jdbc.core.mapping.JdbcValue;
4141
import org.springframework.data.jdbc.support.JdbcUtil;
42+
import org.springframework.data.relational.core.dialect.SqlTypeResolver;
4243
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
4344
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
4445
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
@@ -91,43 +92,6 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
9192
private final CachedResultSetExtractorFactory cachedResultSetExtractorFactory;
9293
private final ValueExpressionDelegate delegate;
9394

94-
/**
95-
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
96-
* and {@link RowMapper}.
97-
*
98-
* @param queryMethod must not be {@literal null}.
99-
* @param operations must not be {@literal null}.
100-
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
101-
* @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
102-
*/
103-
@Deprecated(since = "3.4")
104-
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
105-
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter,
106-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
107-
this(queryMethod.getRequiredQuery(), queryMethod, operations, result -> (RowMapper<Object>) defaultRowMapper,
108-
converter, evaluationContextProvider);
109-
}
110-
111-
/**
112-
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
113-
* and {@link RowMapperFactory}.
114-
*
115-
* @param queryMethod must not be {@literal null}.
116-
* @param operations must not be {@literal null}.
117-
* @param rowMapperFactory must not be {@literal null}.
118-
* @param converter must not be {@literal null}.
119-
* @param evaluationContextProvider must not be {@literal null}.
120-
* @since 2.3
121-
* @deprecated use alternative constructor
122-
*/
123-
@Deprecated(since = "3.4")
124-
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
125-
RowMapperFactory rowMapperFactory, JdbcConverter converter,
126-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
127-
this(queryMethod.getRequiredQuery(), queryMethod, operations, rowMapperFactory, converter,
128-
evaluationContextProvider);
129-
}
130-
13195
/**
13296
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
13397
* and {@link RowMapperFactory}.
@@ -197,28 +161,6 @@ public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedPara
197161
this.delegate = delegate;
198162
}
199163

200-
/**
201-
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
202-
* and {@link RowMapperFactory}.
203-
*
204-
* @param query must not be {@literal null} or empty.
205-
* @param queryMethod must not be {@literal null}.
206-
* @param operations must not be {@literal null}.
207-
* @param rowMapperFactory must not be {@literal null}.
208-
* @param converter must not be {@literal null}.
209-
* @param evaluationContextProvider must not be {@literal null}.
210-
* @since 3.4
211-
* @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
212-
*/
213-
@Deprecated(since = "3.4")
214-
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
215-
RowMapperFactory rowMapperFactory, JdbcConverter converter,
216-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
217-
this(query, queryMethod, operations, rowMapperFactory, converter, new CachingValueExpressionDelegate(
218-
new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), rootObject -> evaluationContextProvider
219-
.getEvaluationContext(queryMethod.getParameters(), new Object[] { rootObject })),
220-
ValueExpressionParser.create()));
221-
}
222164

223165
@Override
224166
public Object execute(Object[] objects) {

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
264264
*/
265265
JdbcQueryMethod getJdbcQueryMethod(Method method, RepositoryMetadata repositoryMetadata,
266266
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
267-
return new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries, getMappingContext());
267+
return new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries, getMappingContext(), getDialect().getSqlTypeResolver());
268268
}
269269

270270
/**

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethodUnitTests.java

+53-6
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,23 @@
2020
import static org.mockito.Mockito.*;
2121

2222
import java.lang.reflect.Method;
23+
import java.sql.JDBCType;
2324
import java.sql.ResultSet;
25+
import java.sql.SQLType;
26+
import java.sql.Types;
27+
import java.util.List;
2428
import java.util.Properties;
2529

30+
import org.assertj.core.api.Assertions;
2631
import org.junit.jupiter.api.BeforeEach;
2732
import org.junit.jupiter.api.Test;
2833
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
2934
import org.springframework.data.projection.ProjectionFactory;
35+
import org.springframework.data.relational.core.dialect.DefaultSqlTypeResolver;
36+
import org.springframework.data.relational.core.dialect.SqlTypeResolver;
3037
import org.springframework.data.relational.core.sql.LockMode;
3138
import org.springframework.data.relational.repository.Lock;
39+
import org.springframework.data.relational.repository.query.SqlType;
3240
import org.springframework.data.repository.core.NamedQueries;
3341
import org.springframework.data.repository.core.RepositoryMetadata;
3442
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
@@ -43,6 +51,7 @@
4351
* @author Moises Cisneros
4452
* @author Mark Paluch
4553
* @author Diego Krupitza
54+
* @author Mikhail Polivakha
4655
*/
4756
public class JdbcQueryMethodUnitTests {
4857

@@ -66,6 +75,8 @@ public void before() {
6675
namedQueries = new PropertiesBasedNamedQueries(properties);
6776

6877
metadata = mock(RepositoryMetadata.class);
78+
when(metadata.getDomainTypeInformation()).then(invocationOnMock -> TypeInformation.of(Object.class));
79+
6980
doReturn(String.class).when(metadata).getReturnedDomainClass(any(Method.class));
7081
doReturn(TypeInformation.of(String.class)).when(metadata).getReturnType(any(Method.class));
7182
}
@@ -78,6 +89,31 @@ public void returnsSqlStatement() throws NoSuchMethodException {
7889
assertThat(queryMethod.getDeclaredQuery()).isEqualTo(QUERY);
7990
}
8091

92+
@Test // DATAJDBC-165
93+
public void testSqlTypeResolver() throws NoSuchMethodException {
94+
95+
JdbcQueryMethod queryMethod = createJdbcQueryMethod(
96+
"findUserTestMethod",
97+
new DefaultSqlTypeResolver(),
98+
Integer.class, String.class, List.class
99+
);
100+
101+
JdbcParameters parameters = queryMethod.getParameters();
102+
103+
SQLType first = parameters.getParameter(0).getSqlType();
104+
SQLType second = parameters.getParameter(1).getSqlType();
105+
SQLType thirdActual = parameters.getParameter(2).getActualSqlType();
106+
107+
Assertions.assertThat(first.getName()).isEqualTo(JDBCType.TINYINT.getName());
108+
Assertions.assertThat(first.getVendorTypeNumber()).isEqualTo(Types.TINYINT);
109+
110+
Assertions.assertThat(second.getName()).isEqualTo(JDBCType.VARCHAR.getName());
111+
Assertions.assertThat(second.getVendorTypeNumber()).isEqualTo(Types.VARCHAR);
112+
113+
Assertions.assertThat(thirdActual.getName()).isEqualTo(JDBCType.SMALLINT.getName());
114+
Assertions.assertThat(thirdActual.getVendorTypeNumber()).isEqualTo(Types.SMALLINT);
115+
}
116+
81117
@Test // DATAJDBC-165
82118
public void returnsSpecifiedRowMapperClass() throws NoSuchMethodException {
83119

@@ -102,12 +138,6 @@ public void returnsSpecifiedSqlStatementIfNameAndValueAreGiven() throws NoSuchMe
102138

103139
}
104140

105-
private JdbcQueryMethod createJdbcQueryMethod(String methodName) throws NoSuchMethodException {
106-
107-
Method method = JdbcQueryMethodUnitTests.class.getDeclaredMethod(methodName);
108-
return new JdbcQueryMethod(method, metadata, mock(ProjectionFactory.class), namedQueries, mappingContext);
109-
}
110-
111141
@Test // DATAJDBC-234
112142
public void returnsImplicitlyNamedQuery() throws NoSuchMethodException {
113143

@@ -148,10 +178,27 @@ void returnsQueryMethodWithCorrectLockTypeNoLock() throws NoSuchMethodException
148178
assertThat(queryMethodWithWriteLock.lookupLockAnnotation()).isEmpty();
149179
}
150180

181+
private JdbcQueryMethod createJdbcQueryMethod(String methodName) throws NoSuchMethodException {
182+
return createJdbcQueryMethod(methodName, new DefaultSqlTypeResolver());
183+
}
184+
185+
private JdbcQueryMethod createJdbcQueryMethod(String methodName, SqlTypeResolver sqlTypeResolver, Class<?>... args) throws NoSuchMethodException {
186+
187+
Method method = JdbcQueryMethodUnitTests.class.getDeclaredMethod(methodName, args);
188+
return new JdbcQueryMethod(method, metadata, mock(ProjectionFactory.class), namedQueries, mappingContext, sqlTypeResolver);
189+
}
190+
151191
@Lock(LockMode.PESSIMISTIC_WRITE)
152192
@Query
153193
private void queryMethodWithWriteLock() {}
154194

195+
@Query
196+
private void findUserTestMethod(
197+
@SqlType(name = "TINYINT", vendorTypeNumber = Types.TINYINT) Integer age,
198+
String name,
199+
List<@SqlType(name = "SMALLINT", vendorTypeNumber = Types.SMALLINT) Integer> statuses
200+
) {}
201+
155202
@Lock(LockMode.PESSIMISTIC_READ)
156203
@Query
157204
private void queryMethodWithReadLock() {}

0 commit comments

Comments
 (0)