Update @Query argument conversion to handle Collection<Enum>.
+ Copy logic from QueryMapper#convertToJdbcValue to resolve Iterable arguments on findBy* query methods to resolve the same for @Query. + Use parameter ResolvableType instead of Class to retain generics info. Original pull request #1226 Closes #1212
This commit is contained in:
committed by
Jens Schauder
parent
ad058a029f
commit
4fb0ccf721
@@ -287,8 +287,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc
|
||||
return writeJdbcValue(value, columnType, JdbcUtil.jdbcTypeFor(sqlType));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.convert.JdbcConverter#writeValue(java.lang.Object, java.lang.Class, int)
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -19,9 +19,12 @@ import static org.springframework.data.jdbc.repository.query.JdbcQueryExecution.
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.sql.SQLType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
@@ -29,6 +32,7 @@ import org.springframework.data.jdbc.core.mapping.JdbcValue;
|
||||
import org.springframework.data.jdbc.support.JdbcUtil;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
|
||||
import org.springframework.data.relational.repository.query.RelationalParameters;
|
||||
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
@@ -53,6 +57,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Maciej Walkowiak
|
||||
* @author Mark Paluch
|
||||
* @author Hebert Coelho
|
||||
* @author Chirag Tailor
|
||||
* @since 2.0
|
||||
*/
|
||||
public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
@@ -157,11 +162,34 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
||||
|
||||
String parameterName = p.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
|
||||
|
||||
Class<?> parameterType = queryMethod.getParameters().getParameter(p.getIndex()).getType();
|
||||
Class<?> conversionTargetType = JdbcColumnTypes.INSTANCE.resolvePrimitiveType(parameterType);
|
||||
RelationalParameters.RelationalParameter parameter = queryMethod.getParameters().getParameter(p.getIndex());
|
||||
ResolvableType resolvableType = parameter.getResolvableType();
|
||||
Class<?> type = resolvableType.resolve();
|
||||
Assert.notNull(type, "@Query parameter could not be resolved!");
|
||||
|
||||
JdbcValue jdbcValue = converter.writeJdbcValue(value, conversionTargetType,
|
||||
JdbcUtil.targetSqlTypeFor(conversionTargetType));
|
||||
JdbcValue jdbcValue;
|
||||
if (value instanceof Iterable) {
|
||||
|
||||
List<Object> mapped = new ArrayList<>();
|
||||
SQLType jdbcType = null;
|
||||
|
||||
Class<?> elementType = resolvableType.getGeneric(0).resolve();
|
||||
Assert.notNull(elementType, "@Query Iterable parameter generic type could not be resolved!");
|
||||
for (Object o : (Iterable<?>) value) {
|
||||
JdbcValue elementJdbcValue = converter.writeJdbcValue(o, elementType,
|
||||
JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(elementType)));
|
||||
if (jdbcType == null) {
|
||||
jdbcType = elementJdbcValue.getJdbcType();
|
||||
}
|
||||
|
||||
mapped.add(elementJdbcValue.getValue());
|
||||
}
|
||||
|
||||
jdbcValue = JdbcValue.of(mapped, jdbcType);
|
||||
} else {
|
||||
jdbcValue = converter.writeJdbcValue(value, type,
|
||||
JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(type)));
|
||||
}
|
||||
|
||||
SQLType jdbcType = jdbcValue.getJdbcType();
|
||||
if (jdbcType == null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2021 the original author or authors.
|
||||
* Copyright 2019-2022 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.jdbc.repository;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.SoftAssertions.*;
|
||||
import static org.springframework.test.context.TestExecutionListeners.MergeMode.*;
|
||||
@@ -23,6 +24,7 @@ import static org.springframework.test.context.TestExecutionListeners.MergeMode.
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.JDBCType;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -36,6 +38,7 @@ import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcValue;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
|
||||
import org.springframework.data.jdbc.testing.AssumeFeatureTestExecutionListener;
|
||||
import org.springframework.data.jdbc.testing.TestConfiguration;
|
||||
@@ -50,6 +53,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Sanghyuk Jung
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
@@ -69,18 +73,19 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
EntityWithBooleanRepository repository() {
|
||||
return factory.getRepository(EntityWithBooleanRepository.class);
|
||||
EntityWithStringyBigDecimalRepository repository() {
|
||||
return factory.getRepository(EntityWithStringyBigDecimalRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
JdbcCustomConversions jdbcCustomConversions() {
|
||||
return new JdbcCustomConversions(asList(StringToBigDecimalConverter.INSTANCE, BigDecimalToString.INSTANCE,
|
||||
CustomIdReadingConverter.INSTANCE, CustomIdWritingConverter.INSTANCE));
|
||||
CustomIdReadingConverter.INSTANCE, CustomIdWritingConverter.INSTANCE, DirectionToIntegerConverter.INSTANCE,
|
||||
NumberToDirectionConverter.INSTANCE, IntegerToDirectionConverter.INSTANCE));
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired EntityWithBooleanRepository repository;
|
||||
@Autowired EntityWithStringyBigDecimalRepository repository;
|
||||
|
||||
/**
|
||||
* In PostrgreSQL this fails if a simple converter like the following is used.
|
||||
@@ -143,13 +148,50 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
|
||||
});
|
||||
}
|
||||
|
||||
interface EntityWithBooleanRepository extends CrudRepository<EntityWithStringyBigDecimal, CustomId> {}
|
||||
@Test // GH-1212
|
||||
void queryByEnumTypeIn() {
|
||||
|
||||
EntityWithStringyBigDecimal entityA = new EntityWithStringyBigDecimal();
|
||||
entityA.direction = Direction.LEFT;
|
||||
EntityWithStringyBigDecimal entityB = new EntityWithStringyBigDecimal();
|
||||
entityB.direction = Direction.CENTER;
|
||||
EntityWithStringyBigDecimal entityC = new EntityWithStringyBigDecimal();
|
||||
entityC.direction = Direction.RIGHT;
|
||||
repository.saveAll(asList(entityA, entityB, entityC));
|
||||
|
||||
assertThat(repository.findByEnumTypeIn(asList(Direction.LEFT, Direction.RIGHT)))
|
||||
.extracting(entity -> entity.direction).containsExactlyInAnyOrder(Direction.LEFT, Direction.RIGHT);
|
||||
}
|
||||
|
||||
@Test // GH-1212
|
||||
void queryByEnumTypeEqual() {
|
||||
|
||||
EntityWithStringyBigDecimal entityA = new EntityWithStringyBigDecimal();
|
||||
entityA.direction = Direction.LEFT;
|
||||
EntityWithStringyBigDecimal entityB = new EntityWithStringyBigDecimal();
|
||||
entityB.direction = Direction.CENTER;
|
||||
EntityWithStringyBigDecimal entityC = new EntityWithStringyBigDecimal();
|
||||
entityC.direction = Direction.RIGHT;
|
||||
repository.saveAll(asList(entityA, entityB, entityC));
|
||||
|
||||
assertThat(repository.findByEnumTypeIn(singletonList(Direction.CENTER))).extracting(entity -> entity.direction)
|
||||
.containsExactly(Direction.CENTER);
|
||||
}
|
||||
|
||||
interface EntityWithStringyBigDecimalRepository extends CrudRepository<EntityWithStringyBigDecimal, CustomId> {
|
||||
@Query("SELECT * FROM ENTITY_WITH_STRINGY_BIG_DECIMAL WHERE DIRECTION IN (:types)")
|
||||
List<EntityWithStringyBigDecimal> findByEnumTypeIn(List<Direction> types);
|
||||
|
||||
@Query("SELECT * FROM ENTITY_WITH_STRINGY_BIG_DECIMAL WHERE DIRECTION = :type")
|
||||
List<EntityWithStringyBigDecimal> findByEnumType(Direction type);
|
||||
}
|
||||
|
||||
private static class EntityWithStringyBigDecimal {
|
||||
|
||||
@Id CustomId id;
|
||||
String stringyNumber;
|
||||
String stringyNumber = "1.0";
|
||||
OtherEntity reference;
|
||||
Direction direction = Direction.CENTER;
|
||||
}
|
||||
|
||||
private static class CustomId {
|
||||
@@ -167,6 +209,10 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
|
||||
Date created;
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
LEFT, CENTER, RIGHT
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
enum StringToBigDecimalConverter implements Converter<String, JdbcValue> {
|
||||
|
||||
@@ -214,4 +260,64 @@ public class JdbcRepositoryCustomConversionIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
enum DirectionToIntegerConverter implements Converter<Direction, JdbcValue> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public JdbcValue convert(Direction source) {
|
||||
|
||||
int integer;
|
||||
switch (source) {
|
||||
case LEFT:
|
||||
integer = -1;
|
||||
break;
|
||||
case CENTER:
|
||||
integer = 0;
|
||||
break;
|
||||
case RIGHT:
|
||||
integer = 1;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return JdbcValue.of(integer, JDBCType.INTEGER);
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter // Needed for Oracle since the JDBC driver returns BigDecimal on read
|
||||
enum NumberToDirectionConverter implements Converter<Number, Direction> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Direction convert(Number source) {
|
||||
int sourceAsInt = source.intValue();
|
||||
if (sourceAsInt == 0) {
|
||||
return Direction.CENTER;
|
||||
} else if (sourceAsInt < 0) {
|
||||
return Direction.LEFT;
|
||||
} else {
|
||||
return Direction.RIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
enum IntegerToDirectionConverter implements Converter<Integer, Direction> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Direction convert(Integer source) {
|
||||
if (source == 0) {
|
||||
return Direction.CENTER;
|
||||
} else if (source < 0) {
|
||||
return Direction.LEFT;
|
||||
} else {
|
||||
return Direction.RIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,6 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.SoftAssertions.*;
|
||||
import static org.springframework.test.context.TestExecutionListeners.MergeMode.*;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.ResultSet;
|
||||
import java.time.Instant;
|
||||
@@ -33,6 +29,7 @@ import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -75,11 +72,16 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.jdbc.JdbcTestUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Very simple use cases for creation and usage of JdbcRepositories.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
@Transactional
|
||||
@TestExecutionListeners(value = AssumeFeatureTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
|
||||
@@ -575,6 +577,38 @@ public class JdbcRepositoryIntegrationTests {
|
||||
assertThat(repository.returnInput(null)).isNull();
|
||||
}
|
||||
|
||||
@Test // GH-1212
|
||||
void queryByEnumTypeIn() {
|
||||
|
||||
DummyEntity dummyA = new DummyEntity("dummyA");
|
||||
dummyA.setDirection(Direction.LEFT);
|
||||
DummyEntity dummyB = new DummyEntity("dummyB");
|
||||
dummyB.setDirection(Direction.CENTER);
|
||||
DummyEntity dummyC = new DummyEntity("dummyC");
|
||||
dummyC.setDirection(Direction.RIGHT);
|
||||
repository.saveAll(asList(dummyA, dummyB, dummyC));
|
||||
|
||||
assertThat(repository.findByEnumTypeIn(asList(Direction.LEFT, Direction.RIGHT)))
|
||||
.extracting(DummyEntity::getDirection)
|
||||
.containsExactlyInAnyOrder(Direction.LEFT, Direction.RIGHT);
|
||||
}
|
||||
|
||||
@Test // GH-1212
|
||||
void queryByEnumTypeEqual() {
|
||||
|
||||
DummyEntity dummyA = new DummyEntity("dummyA");
|
||||
dummyA.setDirection(Direction.LEFT);
|
||||
DummyEntity dummyB = new DummyEntity("dummyB");
|
||||
dummyB.setDirection(Direction.CENTER);
|
||||
DummyEntity dummyC = new DummyEntity("dummyC");
|
||||
dummyC.setDirection(Direction.RIGHT);
|
||||
repository.saveAll(asList(dummyA, dummyB, dummyC));
|
||||
|
||||
assertThat(repository.findByEnumType(Direction.CENTER))
|
||||
.extracting(DummyEntity::getDirection)
|
||||
.containsExactlyInAnyOrder(Direction.CENTER);
|
||||
}
|
||||
|
||||
private Instant createDummyBeforeAndAfterNow() {
|
||||
|
||||
Instant now = Instant.now();
|
||||
@@ -660,6 +694,12 @@ public class JdbcRepositoryIntegrationTests {
|
||||
@Query("SELECT CAST(:hello AS CHAR(5)) FROM DUMMY_ENTITY")
|
||||
@Nullable
|
||||
String returnInput(@Nullable String hello);
|
||||
|
||||
@Query("SELECT * FROM DUMMY_ENTITY WHERE DIRECTION IN (:directions)")
|
||||
List<DummyEntity> findByEnumTypeIn(List<Direction> directions);
|
||||
|
||||
@Query("SELECT * FROM DUMMY_ENTITY WHERE DIRECTION = :direction")
|
||||
List<DummyEntity> findByEnumType(Direction direction);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -713,12 +753,17 @@ public class JdbcRepositoryIntegrationTests {
|
||||
@Id private Long idProp;
|
||||
boolean flag;
|
||||
AggregateReference<DummyEntity, Long> ref;
|
||||
Direction direction;
|
||||
|
||||
public DummyEntity(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
LEFT, CENTER, RIGHT
|
||||
}
|
||||
|
||||
interface DummyProjection {
|
||||
|
||||
String getName();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -15,28 +15,38 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.query;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.JDBCType;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.convert.RelationResolver;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcValue;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
|
||||
@@ -55,6 +65,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author Evgeni Dimitrov
|
||||
* @author Mark Paluch
|
||||
* @author Dennis Effing
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
class StringBasedJdbcQueryUnitTests {
|
||||
|
||||
@@ -64,7 +75,7 @@ class StringBasedJdbcQueryUnitTests {
|
||||
JdbcConverter converter;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws NoSuchMethodException {
|
||||
void setup() {
|
||||
|
||||
this.defaultRowMapper = mock(RowMapper.class);
|
||||
this.operations = mock(NamedParameterJdbcOperations.class);
|
||||
@@ -172,6 +183,54 @@ class StringBasedJdbcQueryUnitTests {
|
||||
.hasMessageContaining("Page queries are not supported using string-based queries");
|
||||
}
|
||||
|
||||
@Test // GH-1212
|
||||
void convertsEnumCollectionParameterIntoStringCollectionParameter() {
|
||||
|
||||
JdbcQueryMethod queryMethod = createMethod("findByEnumTypeIn", Set.class);
|
||||
BasicJdbcConverter converter = new BasicJdbcConverter(mock(RelationalMappingContext.class), mock(RelationResolver.class));
|
||||
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, result -> mock(RowMapper.class), converter);
|
||||
|
||||
query.execute(new Object[] { asList(Direction.LEFT, Direction.RIGHT) });
|
||||
|
||||
ArgumentCaptor<SqlParameterSource> captor = ArgumentCaptor.forClass(SqlParameterSource.class);
|
||||
verify(operations).query(anyString(), captor.capture(), any(ResultSetExtractor.class));
|
||||
|
||||
SqlParameterSource sqlParameterSource = captor.getValue();
|
||||
assertThat(sqlParameterSource.getValue("directions")).asList().containsExactlyInAnyOrder("LEFT", "RIGHT");
|
||||
}
|
||||
|
||||
@Test // GH-1212
|
||||
void convertsEnumCollectionParameterUsingCustomConverterWhenRegisteredForType() {
|
||||
|
||||
JdbcQueryMethod queryMethod = createMethod("findByEnumTypeIn", Set.class);
|
||||
BasicJdbcConverter converter = new BasicJdbcConverter(mock(RelationalMappingContext.class), mock(RelationResolver.class), new JdbcCustomConversions(asList(DirectionToIntegerConverter.INSTANCE, IntegerToDirectionConverter.INSTANCE)), JdbcTypeFactory.unsupported(), IdentifierProcessing.ANSI);
|
||||
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, result -> mock(RowMapper.class), converter);
|
||||
|
||||
query.execute(new Object[] { asList(Direction.LEFT, Direction.RIGHT) });
|
||||
|
||||
ArgumentCaptor<SqlParameterSource> captor = ArgumentCaptor.forClass(SqlParameterSource.class);
|
||||
verify(operations).query(anyString(), captor.capture(), any(ResultSetExtractor.class));
|
||||
|
||||
SqlParameterSource sqlParameterSource = captor.getValue();
|
||||
assertThat(sqlParameterSource.getValue("directions")).asList().containsExactlyInAnyOrder(-1, 1);
|
||||
}
|
||||
|
||||
@Test // GH-1212
|
||||
void doesNotConvertNonCollectionParameter() {
|
||||
|
||||
JdbcQueryMethod queryMethod = createMethod("findBySimpleValue", Integer.class);
|
||||
BasicJdbcConverter converter = new BasicJdbcConverter(mock(RelationalMappingContext.class), mock(RelationResolver.class));
|
||||
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, result -> mock(RowMapper.class), converter);
|
||||
|
||||
query.execute(new Object[] { 1 });
|
||||
|
||||
ArgumentCaptor<SqlParameterSource> captor = ArgumentCaptor.forClass(SqlParameterSource.class);
|
||||
verify(operations).query(anyString(), captor.capture(), any(ResultSetExtractor.class));
|
||||
|
||||
SqlParameterSource sqlParameterSource = captor.getValue();
|
||||
assertThat(sqlParameterSource.getValue("value")).isEqualTo(1);
|
||||
}
|
||||
|
||||
private JdbcQueryMethod createMethod(String methodName, Class<?>... paramTypes) {
|
||||
|
||||
Method method = ReflectionUtils.findMethod(MyRepository.class, methodName, paramTypes);
|
||||
@@ -212,6 +271,11 @@ class StringBasedJdbcQueryUnitTests {
|
||||
@Query(value = "some sql statement")
|
||||
Slice<Object> sliceAll(Pageable pageable);
|
||||
|
||||
@Query(value = "some sql statement")
|
||||
List<Object> findByEnumTypeIn(Set<Direction> directions);
|
||||
|
||||
@Query(value = "some sql statement")
|
||||
List<Object> findBySimpleValue(Integer value);
|
||||
}
|
||||
|
||||
private static class CustomRowMapper implements RowMapper<Object> {
|
||||
@@ -241,6 +305,54 @@ class StringBasedJdbcQueryUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
private enum Direction {
|
||||
LEFT, CENTER, RIGHT
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
enum DirectionToIntegerConverter implements Converter<Direction, JdbcValue> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public JdbcValue convert(Direction source) {
|
||||
|
||||
int integer;
|
||||
switch (source) {
|
||||
case LEFT:
|
||||
integer = -1;
|
||||
break;
|
||||
case CENTER:
|
||||
integer = 0;
|
||||
break;
|
||||
case RIGHT:
|
||||
integer = 1;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return JdbcValue.of(integer, JDBCType.INTEGER);
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
enum IntegerToDirectionConverter implements Converter<Integer, Direction> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Direction convert(Integer source) {
|
||||
|
||||
if (source == 0) {
|
||||
return Direction.CENTER;
|
||||
} else if (source < 0) {
|
||||
return Direction.LEFT;
|
||||
} else {
|
||||
return Direction.RIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DummyEntity {
|
||||
private Long id;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
DROP TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL;
|
||||
DROP TABLE OTHER_ENTITY;
|
||||
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, Stringy_number DECIMAL(20,10));
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
|
||||
CREATE TABLE OTHER_ENTITY ( ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10));
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
|
||||
CREATE TABLE OTHER_ENTITY ( ID IDENTITY PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10));
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
|
||||
CREATE TABLE OTHER_ENTITY ( ID IDENTITY PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT AUTO_INCREMENT PRIMARY KEY, Stringy_number DECIMAL(20,10));
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT AUTO_INCREMENT PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
|
||||
CREATE TABLE OTHER_ENTITY ( ID BIGINT AUTO_INCREMENT PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
DROP TABLE OTHER_ENTITY;
|
||||
DROP TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL;
|
||||
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10));
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id BIGINT IDENTITY PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
|
||||
CREATE TABLE OTHER_ENTITY ( ID BIGINT IDENTITY PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( ID BIGINT AUTO_INCREMENT PRIMARY KEY, Stringy_number DECIMAL(20,10));
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( ID BIGINT AUTO_INCREMENT PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
|
||||
CREATE TABLE OTHER_ENTITY ( ID BIGINT AUTO_INCREMENT PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
|
||||
|
||||
@@ -3,7 +3,8 @@ DROP TABLE OTHER_ENTITY CASCADE CONSTRAINTS PURGE;
|
||||
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL (
|
||||
ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
|
||||
STRINGY_NUMBER DECIMAL(20,10)
|
||||
STRINGY_NUMBER DECIMAL(20,10),
|
||||
DIRECTION INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE OTHER_ENTITY (
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id SERIAL PRIMARY KEY, Stringy_number DECIMAL(20,10));
|
||||
CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( id SERIAL PRIMARY KEY, Stringy_number DECIMAL(20,10), DIRECTION INTEGER);
|
||||
CREATE TABLE OTHER_ENTITY ( ID SERIAL PRIMARY KEY, CREATED DATE, ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER);
|
||||
|
||||
@@ -7,5 +7,6 @@ CREATE TABLE dummy_entity
|
||||
POINT_IN_TIME TIMESTAMP,
|
||||
OFFSET_DATE_TIME TIMESTAMP, -- with time zone is only supported with z/OS
|
||||
FLAG BOOLEAN,
|
||||
REF BIGINT
|
||||
REF BIGINT,
|
||||
DIRECTION VARCHAR(100)
|
||||
);
|
||||
|
||||
@@ -5,5 +5,6 @@ CREATE TABLE dummy_entity
|
||||
POINT_IN_TIME TIMESTAMP,
|
||||
OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE,
|
||||
FLAG BOOLEAN,
|
||||
REF BIGINT
|
||||
REF BIGINT,
|
||||
DIRECTION VARCHAR(100)
|
||||
);
|
||||
|
||||
@@ -5,5 +5,6 @@ CREATE TABLE dummy_entity
|
||||
POINT_IN_TIME TIMESTAMP,
|
||||
OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE,
|
||||
FLAG BOOLEAN,
|
||||
REF BIGINT
|
||||
REF BIGINT,
|
||||
DIRECTION VARCHAR(100)
|
||||
);
|
||||
|
||||
@@ -5,5 +5,6 @@ CREATE TABLE dummy_entity
|
||||
POINT_IN_TIME TIMESTAMP(3),
|
||||
OFFSET_DATE_TIME TIMESTAMP(3),
|
||||
FLAG BOOLEAN,
|
||||
REF BIGINT
|
||||
REF BIGINT,
|
||||
DIRECTION VARCHAR(100)
|
||||
);
|
||||
|
||||
@@ -6,5 +6,6 @@ CREATE TABLE dummy_entity
|
||||
POINT_IN_TIME DATETIME,
|
||||
OFFSET_DATE_TIME DATETIMEOFFSET,
|
||||
FLAG BIT,
|
||||
REF BIGINT
|
||||
REF BIGINT,
|
||||
DIRECTION VARCHAR(100)
|
||||
);
|
||||
|
||||
@@ -8,5 +8,6 @@ CREATE TABLE DUMMY_ENTITY
|
||||
POINT_IN_TIME TIMESTAMP(3) DEFAULT NULL,
|
||||
OFFSET_DATE_TIME TIMESTAMP(3) DEFAULT NULL,
|
||||
FLAG BIT(1),
|
||||
REF BIGINT
|
||||
REF BIGINT,
|
||||
DIRECTION VARCHAR(100)
|
||||
);
|
||||
|
||||
@@ -7,5 +7,6 @@ CREATE TABLE DUMMY_ENTITY
|
||||
POINT_IN_TIME TIMESTAMP,
|
||||
OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE,
|
||||
FLAG NUMBER(1,0),
|
||||
REF NUMBER
|
||||
REF NUMBER,
|
||||
DIRECTION VARCHAR2(100)
|
||||
);
|
||||
|
||||
@@ -6,5 +6,6 @@ CREATE TABLE dummy_entity
|
||||
POINT_IN_TIME TIMESTAMP,
|
||||
OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE,
|
||||
FLAG BOOLEAN,
|
||||
REF BIGINT
|
||||
REF BIGINT,
|
||||
DIRECTION VARCHAR(100)
|
||||
);
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
package org.springframework.data.relational.repository.query;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.relational.repository.query.RelationalParameters.RelationalParameter;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
@@ -65,9 +67,12 @@ public class RelationalParameters extends Parameters<RelationalParameters, Relat
|
||||
* Custom {@link Parameter} implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
public static class RelationalParameter extends Parameter {
|
||||
|
||||
private final MethodParameter parameter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RelationalParameter}.
|
||||
*
|
||||
@@ -75,6 +80,13 @@ public class RelationalParameters extends Parameters<RelationalParameters, Relat
|
||||
*/
|
||||
RelationalParameter(MethodParameter parameter) {
|
||||
super(parameter);
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
|
||||
public ResolvableType getResolvableType() {
|
||||
return ResolvableType
|
||||
.forClassWithGenerics(super.getType(), ResolvableType.forMethodParameter(this.parameter).getGenerics());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user