diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java
index 040367b06a..2b722be9a9 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java
@@ -44,25 +44,6 @@ public DefaultJdbcTypeFactory(JdbcOperations operations) {
this(operations, org.springframework.data.jdbc.core.dialect.JdbcArrayColumns.DefaultSupport.INSTANCE);
}
- /**
- * Creates a new {@link DefaultJdbcTypeFactory}.
- *
- * @param operations must not be {@literal null}.
- * @since 2.3
- * @deprecated use
- * {@link #DefaultJdbcTypeFactory(JdbcOperations, org.springframework.data.jdbc.core.dialect.JdbcArrayColumns)}
- * instead.
- */
- @Deprecated(forRemoval = true, since = "3.5")
- public DefaultJdbcTypeFactory(JdbcOperations operations, JdbcArrayColumns arrayColumns) {
-
- Assert.notNull(operations, "JdbcOperations must not be null");
- Assert.notNull(arrayColumns, "JdbcArrayColumns must not be null");
-
- this.operations = operations;
- this.arrayColumns = arrayColumns;
- }
-
/**
* Creates a new {@link DefaultJdbcTypeFactory}.
*
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java
index 1bec8222f0..11653e7716 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java
@@ -45,6 +45,10 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
private DataAccessStrategy delegate;
+ /**
+ * @deprecated please, use {@link DelegatingDataAccessStrategy#DelegatingDataAccessStrategy(DataAccessStrategy)} instead
+ */
+ @Deprecated(forRemoval = true, since = "4.0")
public DelegatingDataAccessStrategy() {}
public DelegatingDataAccessStrategy(DataAccessStrategy delegate) {
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/DefaultSqlTypeResolver.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/DefaultSqlTypeResolver.java
new file mode 100644
index 0000000000..f36240f51f
--- /dev/null
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/DefaultSqlTypeResolver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.data.jdbc.core.dialect;
+
+import java.sql.SQLType;
+
+import org.springframework.data.relational.repository.query.RelationalParameters;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * Default implementation of {@link SqlTypeResolver}. Capable to resolve the {@link SqlType} annotation
+ * on the {@link java.lang.annotation.ElementType#PARAMETER method parameters}, like this:
+ *
+ *
+ * List findByAge(@SqlType(name = "TINYINT", vendorTypeNumber = Types.TINYINT) byte age);
+ *
+ *
+ * Qualification of the actual {@link SQLType} (the sql type of the component), then the following needs to be done:
+ *
+ * List findByAgeIn(@SqlType(name = "TINYINT", vendorTypeNumber = Types.TINYINT) Integer[] age);
+ *
+ *
+ * @author Mikhail Polivakha
+ */
+public class DefaultSqlTypeResolver implements SqlTypeResolver {
+
+ public static DefaultSqlTypeResolver INSTANCE = new DefaultSqlTypeResolver();
+
+ @Override
+ @Nullable
+ public SQLType resolveSqlType(RelationalParameters.RelationalParameter relationalParameter) {
+ return resolveInternally(relationalParameter);
+ }
+
+ @Override
+ @Nullable
+ public SQLType resolveActualSqlType(RelationalParameters.RelationalParameter relationalParameter) {
+ return resolveInternally(relationalParameter);
+ }
+
+ private static AnnotationBasedSqlType resolveInternally(
+ RelationalParameters.RelationalParameter relationalParameter) {
+ SqlType parameterAnnotation = relationalParameter.getMethodParameter().getParameterAnnotation(SqlType.class);
+
+ if (parameterAnnotation != null) {
+ return new AnnotationBasedSqlType(parameterAnnotation);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * {@link SQLType} determined from the {@link SqlType} annotation.
+ *
+ * @author Mikhail Polivakha
+ */
+ protected static class AnnotationBasedSqlType implements SQLType {
+
+ private final SqlType sqlType;
+
+ public AnnotationBasedSqlType(SqlType sqlType) {
+ Assert.notNull(sqlType, "sqlType must not be null");
+
+ this.sqlType = sqlType;
+ }
+
+ @Override
+ public String getName() {
+ return sqlType.name();
+ }
+
+ @Override
+ public String getVendor() {
+ return "Spring Data JDBC";
+ }
+
+ @Override
+ public Integer getVendorTypeNumber() {
+ return sqlType.vendorTypeNumber();
+ }
+ }
+}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java
index 5728ce4f56..b494d224f9 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java
@@ -16,6 +16,8 @@
package org.springframework.data.jdbc.core.dialect;
import org.springframework.data.relational.core.dialect.Dialect;
+import org.springframework.data.jdbc.core.dialect.SqlTypeResolver;
+import org.springframework.data.jdbc.core.dialect.DefaultSqlTypeResolver;
/**
* {@link org.springframework.data.relational.core.dialect.ArrayColumns} that offer JDBC specific functionality.
@@ -37,4 +39,12 @@ default JdbcArrayColumns getArraySupport() {
return JdbcArrayColumns.Unsupported.INSTANCE;
}
+ /**
+ * Returns a {@link SqlTypeResolver} of this dialect.
+ *
+ * @since 4.0
+ */
+ default SqlTypeResolver getSqlTypeResolver() {
+ return DefaultSqlTypeResolver.INSTANCE;
+ }
}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/SqlType.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/SqlType.java
new file mode 100644
index 0000000000..25d267c49a
--- /dev/null
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/SqlType.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.data.jdbc.core.dialect;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.SQLType;
+
+/**
+ * Serves as a hint to the {@link DefaultSqlTypeResolver}, that signals the {@link java.sql.SQLType} to be used.
+ * The arguments of this annotation are identical to the methods on {@link java.sql.SQLType} interface, expect for
+ * the {@link SQLType#getVendor()}, which is absent, because it typically does not matter as such for the underlying
+ * JDBC drivers. The examples of usage, can be found in javadoc of {@link DefaultSqlTypeResolver}.
+ *
+ * @see DefaultSqlTypeResolver
+ * @author Mikhail Polivakha
+ */
+@Documented
+@Target({ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SqlType {
+
+ /**
+ * Returns the {@code SQLType} name that represents a SQL data type.
+ *
+ * @return The name of this {@code SQLType}.
+ */
+ String name();
+
+ /**
+ * Returns the vendor specific type number for the data type.
+ *
+ * @return An Integer representing the vendor specific data type
+ */
+ int vendorTypeNumber();
+}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/SqlTypeResolver.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/SqlTypeResolver.java
new file mode 100644
index 0000000000..bd845b1564
--- /dev/null
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/SqlTypeResolver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.data.jdbc.core.dialect;
+
+import java.sql.SQLType;
+
+import org.springframework.data.relational.repository.query.RelationalParameters.RelationalParameter;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+
+/**
+ * Common interface for all objects capable to resolve the {@link SQLType} to be used for a give method parameter.
+ *
+ * @author Mikhail Polivakha
+ */
+public interface SqlTypeResolver {
+
+ /**
+ * Resolving the {@link SQLType} from the given {@link RelationalParameter}.
+ *
+ * @param relationalParameter the parameter of the query method
+ * @return {@code null} in case the given {@link SqlTypeResolver} cannot or do not want to determine the
+ * {@link SQLType} of the given parameter
+ */
+ @Nullable
+ SQLType resolveSqlType(RelationalParameter relationalParameter);
+
+ /**
+ * Resolving the {@link SQLType} from the given {@link RelationalParameter}. The definition of "actual"
+ * type can be looked up in the {@link TypeInformation#getActualType()}.
+ *
+ * @param relationalParameter the parameter of the query method
+ * @return {@code null} in case the given {@link SqlTypeResolver} cannot or do not want to determine the
+ * actual {@link SQLType} of the given parameter
+ */
+ @Nullable
+ SQLType resolveActualSqlType(RelationalParameter relationalParameter);
+}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java
index ddfb1e7431..1e9c5752b0 100755
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java
@@ -21,28 +21,47 @@
import org.springframework.core.MethodParameter;
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
import org.springframework.data.jdbc.support.JdbcUtil;
+import org.springframework.data.jdbc.core.dialect.DefaultSqlTypeResolver;
+import org.springframework.data.jdbc.core.dialect.SqlTypeResolver;
import org.springframework.data.relational.repository.query.RelationalParameters;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ParametersSource;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
+import org.springframework.util.Assert;
/**
* Custom extension of {@link RelationalParameters}.
*
* @author Mark Paluch
+ * @author Mikhail Polivakha
* @since 3.2.6
*/
public class JdbcParameters extends RelationalParameters {
/**
- * Creates a new {@link JdbcParameters} instance from the given {@link ParametersSource}.
+ * Creates a new {@link JdbcParameters} instance from the given {@link ParametersSource}. Uses the {@link DefaultSqlTypeResolver}.
*
* @param parametersSource must not be {@literal null}.
*/
public JdbcParameters(ParametersSource parametersSource) {
super(parametersSource,
- methodParameter -> new JdbcParameter(methodParameter, parametersSource.getDomainTypeInformation()));
+ methodParameter -> new JdbcParameter(methodParameter, parametersSource.getDomainTypeInformation(),
+ DefaultSqlTypeResolver.INSTANCE));
+ }
+
+ /**
+ * Creates a new {@link JdbcParameters} instance from the given {@link ParametersSource} and given {@link SqlTypeResolver}.
+ *
+ * @param parametersSource must not be {@literal null}.
+ * @param sqlTypeResolver must not be {@literal null}.
+ */
+ public JdbcParameters(ParametersSource parametersSource, SqlTypeResolver sqlTypeResolver) {
+ super(parametersSource,
+ methodParameter -> new JdbcParameter(methodParameter, parametersSource.getDomainTypeInformation(), sqlTypeResolver));
+
+ Assert.notNull(sqlTypeResolver, "SqlTypeResolver must not be null");
+ Assert.notNull(parametersSource, "ParametersSource must not be null");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@@ -69,7 +88,7 @@ protected JdbcParameters createFrom(List parameters) {
*/
public static class JdbcParameter extends RelationalParameter {
- private final SQLType sqlType;
+ private final Lazy sqlType;
private final Lazy actualSqlType;
/**
@@ -77,19 +96,34 @@ public static class JdbcParameter extends RelationalParameter {
*
* @param parameter must not be {@literal null}.
*/
- JdbcParameter(MethodParameter parameter, TypeInformation> domainType) {
+ JdbcParameter(MethodParameter parameter, TypeInformation> domainType, SqlTypeResolver sqlTypeResolver) {
super(parameter, domainType);
TypeInformation> typeInformation = getTypeInformation();
- sqlType = JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getType()));
+ sqlType = Lazy.of(() -> {
+ SQLType resolvedSqlType = sqlTypeResolver.resolveSqlType(this);
+
+ if (resolvedSqlType == null) {
+ return JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getType()));
+ } else {
+ return resolvedSqlType;
+ }
+ });
+
+ actualSqlType = Lazy.of(() -> {
+ SQLType resolvedActualSqlType = sqlTypeResolver.resolveActualSqlType(this);
- actualSqlType = Lazy.of(() -> JdbcUtil
- .targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getActualType().getType())));
+ if (resolvedActualSqlType == null) {
+ return JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getActualType().getType()));
+ } else {
+ return resolvedActualSqlType;
+ }
+ });
}
public SQLType getSqlType() {
- return sqlType;
+ return sqlType.get();
}
public SQLType getActualSqlType() {
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java
index 7fc7c114c6..3a8dcf5001 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java
@@ -24,6 +24,8 @@
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
+import org.springframework.data.jdbc.core.dialect.DefaultSqlTypeResolver;
+import org.springframework.data.jdbc.core.dialect.SqlTypeResolver;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.repository.Lock;
@@ -34,6 +36,7 @@
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersSource;
import org.springframework.data.repository.query.QueryMethod;
+import org.springframework.data.util.Lazy;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
@@ -53,6 +56,7 @@
* @author Diego Krupitza
* @author Mark Paluch
* @author Daeho Kwon
+ * @author Mikhail Polivakha
*/
public class JdbcQueryMethod extends QueryMethod {
@@ -67,8 +71,15 @@ public class JdbcQueryMethod extends QueryMethod {
public JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries,
MappingContext extends RelationalPersistentEntity>, ? extends RelationalPersistentProperty> mappingContext) {
+ this(method, metadata, factory, namedQueries, mappingContext, DefaultSqlTypeResolver.INSTANCE);
+ }
+
+ public JdbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
+ NamedQueries namedQueries,
+ MappingContext extends RelationalPersistentEntity>, ? extends RelationalPersistentProperty> mappingContext,
+ SqlTypeResolver sqlTypeResolver) {
- super(method, metadata, factory, JdbcParameters::new);
+ super(method, metadata, factory, parametersSource -> new JdbcParameters(parametersSource, sqlTypeResolver));
this.namedQueries = namedQueries;
this.method = method;
this.mappingContext = mappingContext;
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java
index c6808b0935..3d8831d844 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java
@@ -86,13 +86,13 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
- * and {@link org.springframework.data.jdbc.repository.query.RowMapperFactory}.
+ * and {@link RowMapperFactory}.
*
- * @param queryMethod must not be {@literal null}.
- * @param operations must not be {@literal null}.
+ * @param queryMethod must not be {@literal null}.
+ * @param operations must not be {@literal null}.
* @param rowMapperFactory must not be {@literal null}.
- * @param converter must not be {@literal null}.
- * @param delegate must not be {@literal null}.
+ * @param converter must not be {@literal null}.
+ * @param delegate must not be {@literal null}.
* @since 3.4
*/
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
@@ -105,12 +105,12 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link org.springframework.data.jdbc.repository.query.RowMapperFactory}.
*
- * @param query must not be {@literal null} or empty.
- * @param queryMethod must not be {@literal null}.
- * @param operations must not be {@literal null}.
+ * @param query must not be {@literal null} or empty.
+ * @param queryMethod must not be {@literal null}.
+ * @param operations must not be {@literal null}.
* @param rowMapperFactory must not be {@literal null}.
- * @param converter must not be {@literal null}.
- * @param delegate must not be {@literal null}.
+ * @param converter must not be {@literal null}.
+ * @param delegate must not be {@literal null}.
* @since 3.4
*/
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
@@ -377,7 +377,8 @@ private static boolean isUnconfigured(@Nullable Class> configuredClass, Class<
}
@Deprecated(since = "3.4")
- public void setBeanFactory(BeanFactory beanFactory) {}
+ public void setBeanFactory(BeanFactory beanFactory) {
+ }
class CachedRowMapperFactory {
@@ -436,19 +437,20 @@ public CachedResultSetExtractorFactory(Supplier> resultSetExtractor
String resultSetExtractorRef = getQueryMethod().getResultSetExtractorRef();
Class extends ResultSetExtractor> resultSetExtractorClass = getQueryMethod().getResultSetExtractorClass();
- if (!ObjectUtils.isEmpty(resultSetExtractorRef)
- && !isUnconfigured(resultSetExtractorClass, ResultSetExtractor.class)) {
+ if (!ObjectUtils.isEmpty(resultSetExtractorRef) && !isUnconfigured(resultSetExtractorClass,
+ ResultSetExtractor.class)) {
throw new IllegalArgumentException(
"Invalid ResultSetExtractor configuration. Configure either one but not both via @Query(resultSetExtractorRef = …, resultSetExtractorClass = …) for query method "
+ getQueryMethod());
}
- this.configuredResultSetExtractor = !ObjectUtils.isEmpty(resultSetExtractorRef)
- || !isUnconfigured(resultSetExtractorClass, ResultSetExtractor.class);
+ this.configuredResultSetExtractor =
+ !ObjectUtils.isEmpty(resultSetExtractorRef) || !isUnconfigured(resultSetExtractorClass,
+ ResultSetExtractor.class);
- this.rowMapperConstructor = resultSetExtractorClass != null
- ? ClassUtils.getConstructorIfAvailable(resultSetExtractorClass, RowMapper.class)
- : null;
+ this.rowMapperConstructor = resultSetExtractorClass != null ?
+ ClassUtils.getConstructorIfAvailable(resultSetExtractorClass, RowMapper.class) :
+ null;
this.constructor = resultSetExtractorClass != null ? findPrimaryConstructor(resultSetExtractorClass) : null;
this.resultSetExtractorFactory = rowMapper -> {
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java
index 0d9099bd2f..5ebc122a38 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java
@@ -23,6 +23,7 @@
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
+import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.repository.query.DefaultRowMapperFactory;
import org.springframework.data.jdbc.repository.query.JdbcQueryMethod;
import org.springframework.data.jdbc.repository.query.PartTreeJdbcQuery;
@@ -69,7 +70,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
protected final ValueExpressionDelegate delegate;
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
- RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
+ RelationalMappingContext context, JdbcConverter converter, JdbcDialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
ValueExpressionDelegate delegate) {
@@ -105,7 +106,7 @@ static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy {
private final RowMapperFactory rowMapperFactory;
CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
- RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
+ RelationalMappingContext context, JdbcConverter converter, JdbcDialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
ValueExpressionDelegate delegate) {
@@ -138,7 +139,7 @@ static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy {
private final RowMapperFactory rowMapperFactory;
DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
- RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
+ RelationalMappingContext context, JdbcConverter converter, JdbcDialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
@Nullable BeanFactory beanfactory, ValueExpressionDelegate delegate) {
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, delegate);
@@ -191,7 +192,7 @@ static class CreateIfNotFoundQueryLookupStrategy extends JdbcQueryLookupStrategy
* @param lookupStrategy must not be {@literal null}.
*/
CreateIfNotFoundQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
- RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
+ RelationalMappingContext context, JdbcConverter converter, JdbcDialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy,
ValueExpressionDelegate delegate) {
@@ -222,7 +223,12 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
*/
JdbcQueryMethod getJdbcQueryMethod(Method method, RepositoryMetadata repositoryMetadata,
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
- return new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries, getMappingContext());
+ return new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries, getMappingContext(), getDialect().getSqlTypeResolver());
+ }
+
+ @Override
+ public JdbcDialect getDialect() {
+ return (JdbcDialect) super.getDialect();
}
/**
@@ -238,7 +244,9 @@ JdbcQueryMethod getJdbcQueryMethod(Method method, RepositoryMetadata repositoryM
* @param queryMappingConfiguration must not be {@literal null}
* @param operations must not be {@literal null}
* @param beanFactory may be {@literal null}
+ * @deprecated please, use {@link #create(Key, ApplicationEventPublisher, EntityCallbacks, RelationalMappingContext, JdbcConverter, JdbcDialect, QueryMappingConfiguration, NamedParameterJdbcOperations, BeanFactory, ValueExpressionDelegate)}
*/
+ @Deprecated(forRemoval = true, since = "4.0")
public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPublisher publisher,
@Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
@@ -250,6 +258,52 @@ public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPubl
Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
+ Assert.state(dialect instanceof JdbcDialect, "Dialect must be an instance of the JdbcDialect");
+
+ CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, context,
+ converter, (JdbcDialect) dialect, queryMappingConfiguration, operations, delegate);
+
+ DeclaredQueryLookupStrategy declaredQueryLookupStrategy = new DeclaredQueryLookupStrategy(publisher, callbacks,
+ context, converter, (JdbcDialect) dialect, queryMappingConfiguration, operations, beanFactory, delegate);
+
+ Key keyToUse = key != null ? key : Key.CREATE_IF_NOT_FOUND;
+
+ LOG.debug(String.format("Using the queryLookupStrategy %s", keyToUse));
+
+ return switch (keyToUse) {
+ case CREATE -> createQueryLookupStrategy;
+ case USE_DECLARED_QUERY -> declaredQueryLookupStrategy;
+ case CREATE_IF_NOT_FOUND ->
+ new CreateIfNotFoundQueryLookupStrategy(publisher, callbacks, context, converter, (JdbcDialect) dialect,
+ queryMappingConfiguration, operations, createQueryLookupStrategy, declaredQueryLookupStrategy, delegate);
+ };
+ }
+
+ /**
+ * Creates a {@link QueryLookupStrategy} based on the provided
+ * {@link org.springframework.data.repository.query.QueryLookupStrategy.Key}.
+ *
+ * @param key the key that decides what {@link QueryLookupStrategy} shozld be used.
+ * @param publisher must not be {@literal null}
+ * @param callbacks may be {@literal null}
+ * @param context must not be {@literal null}
+ * @param converter must not be {@literal null}
+ * @param dialect must not be {@literal null}
+ * @param queryMappingConfiguration must not be {@literal null}
+ * @param operations must not be {@literal null}
+ * @param beanFactory may be {@literal null}
+ */
+ public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPublisher publisher,
+ @Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, JdbcDialect dialect,
+ QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
+ @Nullable BeanFactory beanFactory, ValueExpressionDelegate delegate) {
+ Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
+ Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
+ Assert.notNull(converter, "JdbcConverter must not be null");
+ Assert.notNull(dialect, "Dialect must not be null");
+ Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
+ Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
+ Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, context,
converter, dialect, queryMappingConfiguration, operations, delegate);
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java
index c9b54ac8b7..8eeda490da 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java
@@ -23,6 +23,7 @@
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
+import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -58,7 +59,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
private final ApplicationEventPublisher publisher;
private final DataAccessStrategy accessStrategy;
private final NamedParameterJdbcOperations operations;
- private final Dialect dialect;
+ private final JdbcDialect dialect;
private @Nullable BeanFactory beanFactory;
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
@@ -75,10 +76,41 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
* @param publisher must not be {@literal null}.
* @param operations must not be {@literal null}.
*/
+ @Deprecated(forRemoval = true, since = "4.0")
public JdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
JdbcConverter converter, Dialect dialect, ApplicationEventPublisher publisher,
NamedParameterJdbcOperations operations) {
+ Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null");
+ Assert.notNull(context, "RelationalMappingContext must not be null");
+ Assert.notNull(converter, "RelationalConverter must not be null");
+ Assert.notNull(dialect, "Dialect must not be null");
+ Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
+ Assert.state(dialect instanceof JdbcDialect, "Dialect must be an instance of the JdbcDialect");
+
+ this.publisher = publisher;
+ this.context = context;
+ this.converter = converter;
+ this.dialect = (JdbcDialect) dialect;
+ this.accessStrategy = dataAccessStrategy;
+ this.operations = operations;
+ }
+
+ /**
+ * Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy},
+ * {@link RelationalMappingContext} and {@link ApplicationEventPublisher}.
+ *
+ * @param dataAccessStrategy must not be {@literal null}.
+ * @param context must not be {@literal null}.
+ * @param converter must not be {@literal null}.
+ * @param dialect must not be {@literal null}.
+ * @param publisher must not be {@literal null}.
+ * @param operations must not be {@literal null}.
+ */
+ public JdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
+ JdbcConverter converter, JdbcDialect dialect, ApplicationEventPublisher publisher,
+ NamedParameterJdbcOperations operations) {
+
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null");
Assert.notNull(context, "RelationalMappingContext must not be null");
Assert.notNull(converter, "RelationalConverter must not be null");
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java
index 786f32f373..16ca826bea 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java
@@ -18,6 +18,7 @@
import java.io.Serializable;
import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
@@ -28,6 +29,7 @@
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.convert.SqlParametersFactory;
+import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -38,7 +40,7 @@
import org.springframework.util.Assert;
/**
- * Special adapter for Springs {@link org.springframework.beans.factory.FactoryBean} interface to allow easy setup of
+ * Special adapter for Springs {@link FactoryBean} interface to allow easy setup of
* repository factories via Spring configuration.
*
* @author Jens Schauder
@@ -61,7 +63,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
private NamedParameterJdbcOperations operations;
private EntityCallbacks entityCallbacks;
- private Dialect dialect;
+ private JdbcDialect dialect;
/**
* Creates a new {@link JdbcRepositoryFactoryBean} for the given repository interface.
@@ -103,8 +105,20 @@ public void setMappingContext(RelationalMappingContext mappingContext) {
this.mappingContext = mappingContext;
}
+ /**
+ * @deprecated please, use {@link #setDialect(JdbcDialect)} instead.
+ */
+ @Deprecated(forRemoval = true, since = "4.0")
public void setDialect(Dialect dialect) {
+ Assert.notNull(dialect, "Dialect must not be null");
+ Assert.state(dialect instanceof JdbcDialect, "Dialect must be an instance of the JdbcDialect");
+
+ this.setDialect(((JdbcDialect) dialect));
+ }
+
+ public void setDialect(JdbcDialect dialect) {
+
Assert.notNull(dialect, "Dialect must not be null");
this.dialect = dialect;
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java
index 0abd9c745d..305edc70d2 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java
@@ -31,6 +31,7 @@
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
+import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.dialect.JdbcHsqlDbDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.query.Query;
@@ -94,15 +95,15 @@ private String query() {
private @NotNull T repository(Class repositoryInterface) {
- Dialect dialect = JdbcHsqlDbDialect.INSTANCE;
+ JdbcDialect dialect = JdbcHsqlDbDialect.INSTANCE;
RelationalMappingContext context = JdbcMappingContext.forQuotedIdentifiers();
+ DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class);
- DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy();
+ DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy(dataAccessStrategy);
JdbcConverter converter = new MappingJdbcConverter(context, delegatingDataAccessStrategy,
new JdbcCustomConversions(), new DefaultJdbcTypeFactory(operations.getJdbcOperations()));
- DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class);
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, converter, dialect,
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
index be5b194ffa..b641212251 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
@@ -15,11 +15,16 @@
*/
package org.springframework.data.jdbc.repository;
-import static java.util.Arrays.*;
-import static org.assertj.core.api.Assertions.*;
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
-import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.HashMap;
@@ -33,7 +38,16 @@
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
-import org.springframework.data.jdbc.core.convert.*;
+import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
+import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
+import org.springframework.data.jdbc.core.convert.DelegatingDataAccessStrategy;
+import org.springframework.data.jdbc.core.convert.InsertStrategyFactory;
+import org.springframework.data.jdbc.core.convert.JdbcConverter;
+import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
+import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
+import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
+import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
+import org.springframework.data.jdbc.core.convert.SqlParametersFactory;
import org.springframework.data.jdbc.core.dialect.JdbcH2Dialect;
import org.springframework.data.jdbc.core.dialect.JdbcHsqlDbDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethodUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethodUnitTests.java
index ef3b390d52..7986bafc2a 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethodUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethodUnitTests.java
@@ -20,15 +20,22 @@
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
+import java.sql.JDBCType;
import java.sql.ResultSet;
+import java.sql.SQLType;
+import java.sql.Types;
import java.util.Properties;
+import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.projection.ProjectionFactory;
+import org.springframework.data.jdbc.core.dialect.DefaultSqlTypeResolver;
+import org.springframework.data.jdbc.core.dialect.SqlTypeResolver;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.data.relational.repository.Lock;
+import org.springframework.data.jdbc.core.dialect.SqlType;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
@@ -43,6 +50,7 @@
* @author Moises Cisneros
* @author Mark Paluch
* @author Diego Krupitza
+ * @author Mikhail Polivakha
*/
public class JdbcQueryMethodUnitTests {
@@ -66,6 +74,8 @@ public void before() {
namedQueries = new PropertiesBasedNamedQueries(properties);
metadata = mock(RepositoryMetadata.class);
+ when(metadata.getDomainTypeInformation()).then(invocationOnMock -> TypeInformation.of(Object.class));
+
doReturn(String.class).when(metadata).getReturnedDomainClass(any(Method.class));
doReturn(TypeInformation.of(String.class)).when(metadata).getReturnType(any(Method.class));
}
@@ -78,6 +88,31 @@ public void returnsSqlStatement() throws NoSuchMethodException {
assertThat(queryMethod.getDeclaredQuery()).isEqualTo(QUERY);
}
+ @Test // DATAJDBC-165
+ public void testSqlTypeResolver() throws NoSuchMethodException {
+
+ JdbcQueryMethod queryMethod = createJdbcQueryMethod(
+ "findUserTestMethod",
+ new DefaultSqlTypeResolver(),
+ Integer.class, String.class, Integer[].class
+ );
+
+ JdbcParameters parameters = queryMethod.getParameters();
+
+ SQLType first = parameters.getParameter(0).getSqlType();
+ SQLType second = parameters.getParameter(1).getSqlType();
+ SQLType thirdActual = parameters.getParameter(2).getActualSqlType();
+
+ Assertions.assertThat(first.getName()).isEqualTo(JDBCType.TINYINT.getName());
+ Assertions.assertThat(first.getVendorTypeNumber()).isEqualTo(Types.TINYINT);
+
+ Assertions.assertThat(second.getName()).isEqualTo(JDBCType.VARCHAR.getName());
+ Assertions.assertThat(second.getVendorTypeNumber()).isEqualTo(Types.VARCHAR);
+
+ Assertions.assertThat(thirdActual.getName()).isEqualTo(JDBCType.SMALLINT.getName());
+ Assertions.assertThat(thirdActual.getVendorTypeNumber()).isEqualTo(Types.SMALLINT);
+ }
+
@Test // DATAJDBC-165
public void returnsSpecifiedRowMapperClass() throws NoSuchMethodException {
@@ -102,12 +137,6 @@ public void returnsSpecifiedSqlStatementIfNameAndValueAreGiven() throws NoSuchMe
}
- private JdbcQueryMethod createJdbcQueryMethod(String methodName) throws NoSuchMethodException {
-
- Method method = JdbcQueryMethodUnitTests.class.getDeclaredMethod(methodName);
- return new JdbcQueryMethod(method, metadata, mock(ProjectionFactory.class), namedQueries, mappingContext);
- }
-
@Test // DATAJDBC-234
public void returnsImplicitlyNamedQuery() throws NoSuchMethodException {
@@ -148,10 +177,27 @@ void returnsQueryMethodWithCorrectLockTypeNoLock() throws NoSuchMethodException
assertThat(queryMethodWithWriteLock.lookupLockAnnotation()).isEmpty();
}
+ private JdbcQueryMethod createJdbcQueryMethod(String methodName) throws NoSuchMethodException {
+ return createJdbcQueryMethod(methodName, new DefaultSqlTypeResolver());
+ }
+
+ private JdbcQueryMethod createJdbcQueryMethod(String methodName, SqlTypeResolver sqlTypeResolver, Class>... args) throws NoSuchMethodException {
+
+ Method method = JdbcQueryMethodUnitTests.class.getDeclaredMethod(methodName, args);
+ return new JdbcQueryMethod(method, metadata, mock(ProjectionFactory.class), namedQueries, mappingContext, sqlTypeResolver);
+ }
+
@Lock(LockMode.PESSIMISTIC_WRITE)
@Query
private void queryMethodWithWriteLock() {}
+ @Query
+ private void findUserTestMethod(
+ @SqlType(name = "TINYINT", vendorTypeNumber = Types.TINYINT) Integer age,
+ String name,
+ @SqlType(name = "SMALLINT", vendorTypeNumber = Types.SMALLINT) Integer[] statuses
+ ) {}
+
@Lock(LockMode.PESSIMISTIC_READ)
@Query
private void queryMethodWithReadLock() {}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java
index 3738e1a6c9..f22e20c9b7 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java
@@ -19,7 +19,13 @@
import static org.assertj.core.api.SoftAssertions.*;
import static org.mockito.Mockito.*;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.lang.reflect.Method;
+import java.sql.JDBCType;
+import java.sql.SQLType;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -37,8 +43,10 @@
import org.springframework.data.jdbc.core.dialect.JdbcH2Dialect;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
+import org.springframework.data.jdbc.repository.query.JdbcParameters.JdbcParameter;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.relational.core.dialect.Escaper;
+import org.springframework.data.jdbc.core.dialect.SqlTypeResolver;
import org.springframework.data.relational.core.mapping.Embedded;
import org.springframework.data.relational.core.mapping.Embedded.Nullable;
import org.springframework.data.relational.core.mapping.MappedCollection;
@@ -46,6 +54,7 @@
import org.springframework.data.relational.core.sql.Column;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.data.relational.repository.Lock;
+import org.springframework.data.relational.repository.query.RelationalParameters;
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
@@ -112,7 +121,8 @@ void createQueryByAggregateReference() throws Exception {
.hasBindValue("hobby_reference", "twentythree");
}
- @Test // GH-922
+ @Test
+ // GH-922
void createQueryWithPessimisticWriteLock() throws Exception {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameAndLastName", String.class, String.class);
@@ -131,7 +141,8 @@ void createQueryWithPessimisticWriteLock() throws Exception {
});
}
- @Test // GH-922
+ @Test
+ // GH-922
void createQueryWithPessimisticReadLock() throws Exception {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameAndAge", String.class, Integer.class);
@@ -218,8 +229,9 @@ void createsQueryToFindAllEntitiesByProjectionAttribute() throws Exception {
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }), returnedType);
- assertThat(query.getQuery()).isEqualTo("SELECT " + TABLE + ".\"FIRST_NAME\" AS \"FIRST_NAME\" FROM \"users\""
- + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name");
+ assertThat(query.getQuery()).isEqualTo(
+ "SELECT " + TABLE + ".\"FIRST_NAME\" AS \"FIRST_NAME\" FROM \"users\"" + " WHERE " + TABLE
+ + ".\"FIRST_NAME\" = :first_name");
}
@Test // DATAJDBC-318
@@ -600,8 +612,8 @@ void throwsExceptionWhenIgnoringCaseIsImpossible() throws Exception {
JdbcQueryMethod queryMethod = getQueryMethod("findByIdIgnoringCase", Long.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
- assertThatIllegalStateException()
- .isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { 1L }), returnedType));
+ assertThatIllegalStateException().isThrownBy(
+ () -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { 1L }), returnedType));
}
@Test // DATAJDBC-318
@@ -610,8 +622,8 @@ void throwsExceptionWhenConditionKeywordIsUnsupported() throws Exception {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByIdIsEmpty");
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
- assertThatIllegalArgumentException()
- .isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[0]), returnedType));
+ assertThatIllegalArgumentException().isThrownBy(
+ () -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[0]), returnedType));
}
@Test // DATAJDBC-318
@@ -620,8 +632,8 @@ void throwsExceptionWhenInvalidNumberOfParameterIsGiven() throws Exception {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
- assertThatIllegalArgumentException()
- .isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[0]), returnedType));
+ assertThatIllegalArgumentException().isThrownBy(
+ () -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[0]), returnedType));
}
@Test // DATAJDBC-318
@@ -636,6 +648,20 @@ void createsQueryWithLimitToFindEntitiesByStringAttribute() throws Exception {
.contains(" WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name LIMIT 3");
}
+ @Test // DATAJDBC-2020
+ public void testCustomSqlTypeResolverApplied() throws Exception {
+
+ JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameAndAgeIn", new TestSqlTypeResolver(), String.class, Integer[].class);
+
+ JdbcParameter firstNameParam = queryMethod.getParameters().getParameter(0);
+ JdbcParameter agesInParam = queryMethod.getParameters().getParameter(1);
+
+ assertThat(firstNameParam.getSqlType().getName()).isEqualTo(JDBCType.CLOB.name());
+ assertThat(firstNameParam.getSqlType().getVendorTypeNumber()).isEqualTo(JDBCType.CLOB.getVendorTypeNumber());
+ assertThat(agesInParam.getActualSqlType().getName()).isEqualTo(JDBCType.TINYINT.name());
+ assertThat(agesInParam.getActualSqlType().getVendorTypeNumber()).isEqualTo(JDBCType.TINYINT.getVendorTypeNumber());
+ }
+
@Test // DATAJDBC-318
void createsQueryToFindFirstEntityByStringAttribute() throws Exception {
@@ -689,8 +715,8 @@ void createsQueryForCountProjection() throws Exception {
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery((getAccessor(queryMethod, new Object[] { "John" })), returnedType);
- assertThat(query.getQuery())
- .isEqualTo("SELECT COUNT(*) FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name");
+ assertThat(query.getQuery()).isEqualTo(
+ "SELECT COUNT(*) FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name");
}
private PartTreeJdbcQuery createQuery(JdbcQueryMethod queryMethod) {
@@ -704,6 +730,14 @@ private JdbcQueryMethod getQueryMethod(String methodName, Class>... parameterT
new SpelAwareProxyProjectionFactory(), new PropertiesBasedNamedQueries(new Properties()), mappingContext);
}
+ private JdbcQueryMethod getQueryMethod(String methodName, SqlTypeResolver sqlTypeResolver, Class>... parameterTypes)
+ throws Exception {
+ Method method = UserRepository.class.getMethod(methodName, parameterTypes);
+ return new JdbcQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
+ new SpelAwareProxyProjectionFactory(), new PropertiesBasedNamedQueries(new Properties()), mappingContext,
+ sqlTypeResolver);
+ }
+
private RelationalParametersParameterAccessor getAccessor(JdbcQueryMethod queryMethod, Object[] values) {
return new RelationalParametersParameterAccessor(queryMethod, values);
}
@@ -721,6 +755,10 @@ interface UserRepository extends Repository {
List findAllByHated(Hobby hobby);
+ List findAllByFirstNameAndAgeIn( //
+ @MySqlType String firstName, //
+ @MyActualSqlType Integer[] ages);
+
List findAllByHatedName(String name);
List findAllByHobbies(Object hobbies);
@@ -800,6 +838,37 @@ interface UserRepository extends Repository {
long countByFirstName(String name);
}
+ @Target(ElementType.PARAMETER)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface MySqlType {
+ }
+
+ @Target(ElementType.PARAMETER)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface MyActualSqlType {
+ }
+
+ static class TestSqlTypeResolver implements SqlTypeResolver {
+
+ @Override
+ public SQLType resolveSqlType(RelationalParameters.RelationalParameter relationalParameter) {
+ if (relationalParameter.getMethodParameter().hasParameterAnnotation(MySqlType.class)) {
+ return JDBCType.CLOB;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public SQLType resolveActualSqlType(RelationalParameters.RelationalParameter relationalParameter) {
+ if (relationalParameter.getMethodParameter().hasParameterAnnotation(MyActualSqlType.class)) {
+ return JDBCType.TINYINT;
+ } else {
+ return null;
+ }
+ }
+ }
+
@Table("users")
static class User {
@@ -830,6 +899,7 @@ record AnotherEmbedded(@MappedCollection(idColumn = "ID", keyColumn = "ORDER_KEY
}
static class Hobby {
- @Id String name;
+ @Id
+ String name;
}
}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java
index 5dd1e8b105..95923457e0 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java
@@ -18,14 +18,20 @@
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.Types;
import java.time.DayOfWeek;
import java.time.Instant;
+import java.sql.SQLType;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -54,11 +60,14 @@
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
+import org.springframework.data.jdbc.repository.query.JdbcParameters.JdbcParameter;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
+import org.springframework.data.jdbc.core.dialect.SqlTypeResolver;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.data.relational.repository.Lock;
+import org.springframework.data.relational.repository.query.RelationalParameters;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
@@ -290,9 +299,25 @@ void lockNotSupported() {
JdbcQueryMethod queryMethod = createMethod("unsupportedWithLock", Long.class);
- assertThatThrownBy(
- () -> new StringBasedJdbcQuery(queryMethod, operations, result -> defaultRowMapper, converter, delegate))
- .isInstanceOf(UnsupportedOperationException.class);
+ assertThatThrownBy(() -> new StringBasedJdbcQuery(queryMethod, operations, result -> defaultRowMapper, converter,
+ delegate)).isInstanceOf(UnsupportedOperationException.class);
+ }
+
+ @Test // GH-2020
+ void testCustomSqlTypeResolution() {
+
+ JdbcQueryMethod queryMethod = createMethod("findByWithSqlTypeResolver", new TestSqlTypResolver(), Integer.class, Integer[].class);
+
+ StringBasedJdbcQuery stringBasedJdbcQuery = new StringBasedJdbcQuery(queryMethod, operations,
+ result -> defaultRowMapper, converter, delegate);
+
+ JdbcParameters parameters = stringBasedJdbcQuery.getQueryMethod().getParameters();
+
+ JdbcParameter tinyInt = parameters.getParameter(0);
+ JdbcParameter smallIntActual = parameters.getParameter(1);
+
+ assertThat(tinyInt.getSqlType()).isEqualTo(JDBCType.TINYINT);
+ assertThat(smallIntActual.getActualSqlType()).isEqualTo(JDBCType.SMALLINT);
}
@Test // GH-1212
@@ -477,6 +502,13 @@ private JdbcQueryMethod createMethod(String methodName, Class>... paramTypes)
new SpelAwareProxyProjectionFactory(), new PropertiesBasedNamedQueries(new Properties()), this.context);
}
+ private JdbcQueryMethod createMethod(String methodName, SqlTypeResolver sqlTypeResolver, Class>... paramTypes) {
+
+ Method method = ReflectionUtils.findMethod(MyRepository.class, methodName, paramTypes);
+ return new JdbcQueryMethod(method, new DefaultRepositoryMetadata(MyRepository.class),
+ new SpelAwareProxyProjectionFactory(), new PropertiesBasedNamedQueries(new Properties()), this.context, sqlTypeResolver);
+ }
+
private StringBasedJdbcQuery createQuery(JdbcQueryMethod queryMethod) {
return createQuery(queryMethod, null, null);
}
@@ -551,6 +583,9 @@ interface MyRepository extends Repository {
@Query(value = "some sql statement")
List findByListContainer(ListContainer value);
+ @Query(value = "SELECT * FROM my_table WHERE t = ? AND f IN (?)")
+ List findByWithSqlTypeResolver(@MySqlType Integer tinyInt, @MyActualSqlType Integer[] smallInt);
+
@Query("SELECT * FROM table WHERE c = :#{myext.testValue} AND c2 = :#{myext.doSomething()}")
Object findBySpelExpression(Object object);
@@ -565,6 +600,35 @@ interface MyRepository extends Repository {
DummyEntity unsupportedWithLock(Long id);
}
+ @Target(ElementType.PARAMETER)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface MySqlType { }
+
+ @Target(ElementType.PARAMETER)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface MyActualSqlType { }
+
+ static class TestSqlTypResolver implements SqlTypeResolver {
+
+ @Override
+ public SQLType resolveSqlType(RelationalParameters.RelationalParameter relationalParameter) {
+ if (relationalParameter.getMethodParameter().hasParameterAnnotation(MySqlType.class)) {
+ return JDBCType.TINYINT;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public SQLType resolveActualSqlType(RelationalParameters.RelationalParameter relationalParameter) {
+ if (relationalParameter.getMethodParameter().hasParameterAnnotation(MyActualSqlType.class)) {
+ return JDBCType.SMALLINT;
+ } else {
+ return null;
+ }
+ }
+ }
+
private static class CustomRowMapper implements RowMapper {
@Override
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
index 3433864f7f..f278e4387d 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java
@@ -38,6 +38,7 @@
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
+import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -64,7 +65,7 @@ public class JdbcRepositoryFactoryBeanUnitTests {
@Mock DataAccessStrategy dataAccessStrategy;
@Mock ApplicationEventPublisher publisher;
@Mock(answer = Answers.RETURNS_DEEP_STUBS) ListableBeanFactory beanFactory;
- @Mock Dialect dialect;
+ @Mock JdbcDialect dialect;
RelationalMappingContext mappingContext;
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
index ebc73a2058..fad78bf30b 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
@@ -35,12 +35,23 @@
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.convert.CustomConversions;
-import org.springframework.data.jdbc.core.convert.*;
+import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
+import org.springframework.data.jdbc.core.convert.DataAccessStrategyFactory;
+import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
+import org.springframework.data.jdbc.core.convert.IdGeneratingEntityCallback;
+import org.springframework.data.jdbc.core.convert.InsertStrategyFactory;
+import org.springframework.data.jdbc.core.convert.JdbcConverter;
+import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
+import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
+import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
+import org.springframework.data.jdbc.core.convert.RelationResolver;
+import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
+import org.springframework.data.jdbc.core.convert.SqlParametersFactory;
+import org.springframework.data.jdbc.core.dialect.DialectResolver;
import org.springframework.data.jdbc.core.dialect.JdbcArrayColumns;
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
-import org.springframework.data.jdbc.repository.config.DialectResolver;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.mapping.callback.EntityCallback;
import org.springframework.data.mapping.callback.EntityCallbacks;
@@ -85,7 +96,7 @@ public class TestConfiguration {
@Bean
JdbcRepositoryFactory jdbcRepositoryFactory(
@Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
- Dialect dialect, JdbcConverter converter, Optional> namedQueries,
+ JdbcDialect dialect, JdbcConverter converter, Optional> namedQueries,
List> callbacks, List evaulationContextExtensions) {
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, converter, dialect,
@@ -190,7 +201,7 @@ public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback(JdbcMappingCont
}
@Bean
- Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
+ JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
return DialectResolver.getDialect(operations.getJdbcOperations());
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalParameters.java b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalParameters.java
index 16a4588a11..c6a0158ba6 100755
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalParameters.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalParameters.java
@@ -66,6 +66,7 @@ protected RelationalParameters createFrom(List parameters)
public static class RelationalParameter extends Parameter {
private final TypeInformation> typeInformation;
+ private final MethodParameter methodParameter;
/**
* Creates a new {@link RelationalParameter}.
@@ -75,7 +76,7 @@ public static class RelationalParameter extends Parameter {
protected RelationalParameter(MethodParameter parameter, TypeInformation> domainType) {
super(parameter, domainType);
this.typeInformation = TypeInformation.fromMethodParameter(parameter);
-
+ this.methodParameter = parameter;
}
public ResolvableType getResolvableType() {
@@ -85,5 +86,9 @@ public ResolvableType getResolvableType() {
public TypeInformation> getTypeInformation() {
return typeInformation;
}
+
+ public MethodParameter getMethodParameter() {
+ return methodParameter;
+ }
}
}