Skip to content
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

GH-2020 Added SqlTypeResolver abstraction #2024

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 @@ -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.relational.core.dialect.DefaultSqlTypeResolver;
import org.springframework.data.relational.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(),
Lazy.of(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, Lazy<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, Lazy<SqlTypeResolver> sqlTypeResolver) {
super(parameter, domainType);

TypeInformation<?> typeInformation = getTypeInformation();

sqlType = JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getType()));
sqlType = Lazy.of(() -> {
SQLType resolvedSqlType = sqlTypeResolver.get().resolveSqlType(this);

if (resolvedSqlType == null) {
return JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getType()));
} else {
return resolvedSqlType;
}
});

actualSqlType = Lazy.of(() -> {
SQLType resolvedActualSqlType = sqlTypeResolver.get().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
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,28 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.relational.core.dialect.DefaultSqlTypeResolver;
import org.springframework.data.relational.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;
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
import org.springframework.data.relational.repository.query.SimpleRelationalEntityMetadata;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameter;
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;
Expand All @@ -52,6 +57,7 @@
* @author Hebert Coelho
* @author Diego Krupitza
* @author Mark Paluch
* @author Mikhail Polivakha
*/
public class JdbcQueryMethod extends QueryMethod {

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

private final SqlTypeResolver sqlTypeResolver;

// TODO: Remove NamedQueries and put it into JdbcQueryLookupStrategy
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);
this.namedQueries = namedQueries;
this.method = method;
this.mappingContext = mappingContext;
this.annotationCache = new ConcurrentReferenceHashMap<>();
this.modifyingQuery = AnnotationUtils.findAnnotation(method, Modifying.class) != null;
this.sqlTypeResolver = sqlTypeResolver;
}

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.relational.core.dialect.SqlTypeResolver;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
Expand Down Expand Up @@ -91,43 +92,6 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
private final CachedResultSetExtractorFactory cachedResultSetExtractorFactory;
private final ValueExpressionDelegate delegate;

/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link RowMapper}.
*
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
* @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
*/
@Deprecated(since = "3.4")
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(queryMethod.getRequiredQuery(), queryMethod, operations, result -> (RowMapper<Object>) defaultRowMapper,
converter, evaluationContextProvider);
}

/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link RowMapperFactory}.
*
* @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 evaluationContextProvider must not be {@literal null}.
* @since 2.3
* @deprecated use alternative constructor
*/
@Deprecated(since = "3.4")
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapperFactory rowMapperFactory, JdbcConverter converter,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(queryMethod.getRequiredQuery(), queryMethod, operations, rowMapperFactory, converter,
evaluationContextProvider);
}

/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link RowMapperFactory}.
Expand Down Expand Up @@ -197,28 +161,6 @@ public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedPara
this.delegate = delegate;
}

/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link 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 rowMapperFactory must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @since 3.4
* @deprecated since 3.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
*/
@Deprecated(since = "3.4")
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapperFactory rowMapperFactory, JdbcConverter converter,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(query, queryMethod, operations, rowMapperFactory, converter, new CachingValueExpressionDelegate(
new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), rootObject -> evaluationContextProvider
.getEvaluationContext(queryMethod.getParameters(), new Object[] { rootObject })),
ValueExpressionParser.create()));
}

@Override
public Object execute(Object[] objects) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ 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());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,23 @@
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.List;
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.relational.core.dialect.DefaultSqlTypeResolver;
import org.springframework.data.relational.core.dialect.SqlTypeResolver;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.data.relational.repository.Lock;
import org.springframework.data.relational.repository.query.SqlType;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
Expand All @@ -43,6 +51,7 @@
* @author Moises Cisneros
* @author Mark Paluch
* @author Diego Krupitza
* @author Mikhail Polivakha
*/
public class JdbcQueryMethodUnitTests {

Expand All @@ -66,6 +75,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));
}
Expand All @@ -78,6 +89,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, List.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 {

Expand All @@ -102,12 +138,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 {

Expand Down Expand Up @@ -148,10 +178,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,
List<@SqlType(name = "SMALLINT", vendorTypeNumber = Types.SMALLINT) Integer> statuses
) {}

@Lock(LockMode.PESSIMISTIC_READ)
@Query
private void queryMethodWithReadLock() {}
Expand Down
Loading
Loading