diff --git a/pom.xml b/pom.xml index 530667df21..fbfcfd4d1c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.1.0-SNAPSHOT + 3.1.0-1343-converter-for-iterable-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 6e018ca17d..0836f1eed3 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.1.0-SNAPSHOT + 3.1.0-1343-converter-for-iterable-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index aa6936d760..64a9398eee 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.1.0-SNAPSHOT + 3.1.0-1343-converter-for-iterable-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.1.0-SNAPSHOT + 3.1.0-1343-converter-for-iterable-SNAPSHOT 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 77c13873db..4ad7767f12 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 @@ -20,6 +20,7 @@ import java.lang.reflect.Constructor; import java.sql.SQLType; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.springframework.beans.BeanUtils; @@ -189,7 +190,7 @@ private void convertAndAddParameter(MapSqlParameterSource parameters, Parameter Assert.notNull(type, "@Query parameter type could not be resolved"); JdbcValue jdbcValue; - if (value instanceof Iterable) { + if (value instanceof Collection && resolvableType.hasGenerics()) { List mapped = new ArrayList<>(); SQLType jdbcType = null; 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 cdef7e3d71..bdce291169 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 @@ -22,12 +22,14 @@ import java.sql.JDBCType; import java.sql.ResultSet; import java.util.ArrayList; +import java.util.Iterator; 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.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -194,57 +196,96 @@ void pageQueryNotSupported() { @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, evaluationContextProvider); + SqlParameterSource sqlParameterSource = forMethod("findByEnumTypeIn", Set.class) + .withArguments(Set.of(Direction.LEFT, Direction.RIGHT)).extractParameterSource(); - query.execute(new Object[] { Set.of(Direction.LEFT, Direction.RIGHT) }); - - ArgumentCaptor 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(List.of(DirectionToIntegerConverter.INSTANCE, IntegerToDirectionConverter.INSTANCE)), - JdbcTypeFactory.unsupported(), IdentifierProcessing.ANSI); - StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, result -> mock(RowMapper.class), - converter, evaluationContextProvider); - - query.execute(new Object[] { Set.of(Direction.LEFT, Direction.RIGHT) }); - - ArgumentCaptor captor = ArgumentCaptor.forClass(SqlParameterSource.class); - verify(operations).query(anyString(), captor.capture(), any(ResultSetExtractor.class)); + SqlParameterSource sqlParameterSource = forMethod("findByEnumTypeIn", Set.class) // + .withCustomConverters(DirectionToIntegerConverter.INSTANCE, IntegerToDirectionConverter.INSTANCE) + .withArguments(Set.of(Direction.LEFT, Direction.RIGHT)) // + .extractParameterSource(); - 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, evaluationContextProvider); + SqlParameterSource sqlParameterSource = forMethod("findBySimpleValue", Integer.class) // + .withArguments(1) // + .extractParameterSource(); - query.execute(new Object[] { 1 }); + assertThat(sqlParameterSource.getValue("value")).isEqualTo(1); + } - ArgumentCaptor captor = ArgumentCaptor.forClass(SqlParameterSource.class); - verify(operations).query(anyString(), captor.capture(), any(ResultSetExtractor.class)); + @Test // GH-1343 + void appliesConverterToIterable() { - SqlParameterSource sqlParameterSource = captor.getValue(); - assertThat(sqlParameterSource.getValue("value")).isEqualTo(1); + SqlParameterSource sqlParameterSource = forMethod("findByListContainer", ListContainer.class) // + .withCustomConverters(ListContainerToStringConverter.INSTANCE) + .withArguments(new ListContainer("one", "two", "three")) // + .extractParameterSource(); + + assertThat(sqlParameterSource.getValue("value")).isEqualTo("one"); + + } + + QueryFixture forMethod(String name, Class... paramTypes) { + return new QueryFixture(createMethod(name, paramTypes)); + } + + private class QueryFixture { + + private final JdbcQueryMethod method; + private Object[] arguments; + private BasicJdbcConverter converter; + + public QueryFixture(JdbcQueryMethod method) { + this.method = method; + } + + public QueryFixture withArguments(Object... arguments) { + + this.arguments = arguments; + + return this; + } + + public SqlParameterSource extractParameterSource() { + + BasicJdbcConverter converter = this.converter == null // + ? new BasicJdbcConverter(mock(RelationalMappingContext.class), // + mock(RelationResolver.class)) + : this.converter; + + StringBasedJdbcQuery query = new StringBasedJdbcQuery(method, operations, result -> mock(RowMapper.class), + converter, evaluationContextProvider); + + query.execute(arguments); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SqlParameterSource.class); + verify(operations).query(anyString(), captor.capture(), any(ResultSetExtractor.class)); + + return captor.getValue(); + } + + public QueryFixture withConverter(BasicJdbcConverter converter) { + + this.converter = converter; + + return this; + } + + public QueryFixture withCustomConverters(Object... converters) { + + return withConverter(new BasicJdbcConverter(mock(RelationalMappingContext.class), mock(RelationResolver.class), + new JdbcCustomConversions(List.of(converters)), JdbcTypeFactory.unsupported(), IdentifierProcessing.ANSI)); + } } private JdbcQueryMethod createMethod(String methodName, Class... paramTypes) { @@ -293,6 +334,9 @@ interface MyRepository extends Repository { @Query(value = "some sql statement") List findBySimpleValue(Integer value); + @Query(value = "some sql statement") + List findByListContainer(ListContainer value); + @Query("SELECT * FROM table WHERE c = :#{myext.testValue} AND c2 = :#{myext.doSomething()}") Object findBySpelExpression(Object object); } @@ -389,6 +433,32 @@ public Direction convert(Integer source) { } } + static class ListContainer implements Iterable { + + private final List values; + + ListContainer(String... values) { + this.values = List.of(values); + } + + @NotNull + @Override + public Iterator iterator() { + return values.iterator(); + } + } + + @WritingConverter + enum ListContainerToStringConverter implements Converter { + + INSTANCE; + + @Override + public String convert(ListContainer source) { + return source.values.get(0); + } + } + private static class DummyEntity { private Long id; diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index ef55d4bd1b..d898cff618 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 3.1.0-SNAPSHOT + 3.1.0-1343-converter-for-iterable-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.1.0-SNAPSHOT + 3.1.0-1343-converter-for-iterable-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 71d009990b..3931477418 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.1.0-SNAPSHOT + 3.1.0-1343-converter-for-iterable-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.1.0-SNAPSHOT + 3.1.0-1343-converter-for-iterable-SNAPSHOT