Skip to content

GH-2020 Added SqlTypeResolver abstraction #2024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,6 @@ public DefaultJdbcTypeFactory(JdbcOperations operations) {
this(operations, org.springframework.data.jdbc.core.dialect.JdbcArrayColumns.DefaultSupport.INSTANCE);
}

/**
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can move this in the separate commit? We have a chance to remove it in spring-data-jdbc 4.0 major release

* 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}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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:
* <p>
* <pre class="code">
* List<User> findByAge(&#64;SqlType(name = "TINYINT", vendorTypeNumber = Types.TINYINT) byte age);
* </pre>
*
* Qualification of the actual {@link SQLType} (the sql type of the component), then the following needs to be done:
* <pre class="code">
* List<User> findByAgeIn(&#64;SqlType(name = "TINYINT", vendorTypeNumber = Types.TINYINT) Integer[] age);
* </pre>
*
* @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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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" })
Expand All @@ -69,27 +88,42 @@ protected JdbcParameters createFrom(List<RelationalParameter> parameters) {
*/
public static class JdbcParameter extends RelationalParameter {

private final SQLType sqlType;
private final Lazy<SQLType> sqlType;
private final Lazy<SQLType> actualSqlType;

/**
* Creates a new {@link 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() {
Expand Down
Loading