From 25974548a03c3644723a2865690c2d627e38fcb8 Mon Sep 17 00:00:00 2001 From: Chirag Tailor Date: Wed, 6 Apr 2022 14:33:25 -0500 Subject: [PATCH 1/2] 1212-query-in-clause-with-enum - Prepare branch --- pom.xml | 2 +- spring-data-jdbc-distribution/pom.xml | 2 +- spring-data-jdbc/pom.xml | 4 ++-- spring-data-r2dbc/pom.xml | 4 ++-- spring-data-relational/pom.xml | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 6b8e78a089..1e5014df00 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.0.0-SNAPSHOT + 3.0.0-1212-query-in-clause-with-enum-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index db3b7ddd1a..8d11cfa4ff 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.0.0-SNAPSHOT + 3.0.0-1212-query-in-clause-with-enum-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 547ff62b8b..017b699139 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.0.0-SNAPSHOT + 3.0.0-1212-query-in-clause-with-enum-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.0.0-SNAPSHOT + 3.0.0-1212-query-in-clause-with-enum-SNAPSHOT diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index 22914016c3..f8bb6e57de 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 3.0.0-SNAPSHOT + 3.0.0-1212-query-in-clause-with-enum-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.0.0-SNAPSHOT + 3.0.0-1212-query-in-clause-with-enum-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 717ac86edf..14b95e6dd4 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.0.0-SNAPSHOT + 3.0.0-1212-query-in-clause-with-enum-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.0.0-SNAPSHOT + 3.0.0-1212-query-in-clause-with-enum-SNAPSHOT From 841277423826de50146a92a47cadfca6fa99922c Mon Sep 17 00:00:00 2001 From: Chirag Tailor Date: Thu, 7 Apr 2022 08:32:45 -0500 Subject: [PATCH 2/2] Update @Query argument conversion to handle Collection. + 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. --- .../jdbc/core/convert/BasicJdbcConverter.java | 3 +- .../query/StringBasedJdbcQuery.java | 38 +++++- ...itoryCustomConversionIntegrationTests.java | 113 ++++++++++++++++-- .../JdbcRepositoryIntegrationTests.java | 55 ++++++++- .../query/StringBasedJdbcQueryUnitTests.java | 106 +++++++++++++++- ...ryCustomConversionIntegrationTests-db2.sql | 2 +- ...oryCustomConversionIntegrationTests-h2.sql | 2 +- ...yCustomConversionIntegrationTests-hsql.sql | 2 +- ...stomConversionIntegrationTests-mariadb.sql | 2 +- ...CustomConversionIntegrationTests-mssql.sql | 2 +- ...CustomConversionIntegrationTests-mysql.sql | 2 +- ...ustomConversionIntegrationTests-oracle.sql | 3 +- ...tomConversionIntegrationTests-postgres.sql | 2 +- .../JdbcRepositoryIntegrationTests-db2.sql | 3 +- .../JdbcRepositoryIntegrationTests-h2.sql | 3 +- .../JdbcRepositoryIntegrationTests-hsql.sql | 3 +- ...JdbcRepositoryIntegrationTests-mariadb.sql | 3 +- .../JdbcRepositoryIntegrationTests-mssql.sql | 3 +- .../JdbcRepositoryIntegrationTests-mysql.sql | 3 +- .../JdbcRepositoryIntegrationTests-oracle.sql | 3 +- ...dbcRepositoryIntegrationTests-postgres.sql | 3 +- .../query/RelationalParameters.java | 12 ++ 22 files changed, 331 insertions(+), 37 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index 0e8f665cf6..720aa5e483 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -267,8 +267,7 @@ public JdbcValue writeJdbcValue(@Nullable Object value, Class columnType, int 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) */ 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 11c7cfe2c1..99e7c82ade 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 @@ -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 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.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 @@ * @author Maciej Walkowiak * @author Mark Paluch * @author Hebert Coelho + * @author Chirag Tailor * @since 2.0 */ public class StringBasedJdbcQuery extends AbstractJdbcQuery { @@ -149,11 +154,34 @@ private void convertAndAddParameter(MapSqlParameterSource parameters, Parameter 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 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) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java index 2f379975b3..ed365d0442 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java @@ -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. @@ -23,6 +23,8 @@ import java.math.BigDecimal; import java.sql.JDBCType; import java.util.Date; +import java.util.List; +import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,6 +38,7 @@ 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 @@ * * @author Jens Schauder * @author Sanghyuk Jung + * @author Chirag Tailor */ @ContextConfiguration @Transactional @@ -69,18 +73,19 @@ Class testClass() { } @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,52 @@ public void saveAndLoadAnEntityWithReference() { }); } - interface EntityWithBooleanRepository extends CrudRepository {} + @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(Set.of(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(Set.of(Direction.CENTER))) + .extracting(entity -> entity.direction) + .containsExactly(Direction.CENTER); + } + + interface EntityWithStringyBigDecimalRepository extends CrudRepository { + @Query("SELECT * FROM ENTITY_WITH_STRINGY_BIG_DECIMAL WHERE DIRECTION IN (:types)") + List findByEnumTypeIn(Set types); + + @Query("SELECT * FROM ENTITY_WITH_STRINGY_BIG_DECIMAL WHERE DIRECTION = :type") + List 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 +211,10 @@ private static class OtherEntity { Date created; } + enum Direction { + LEFT, CENTER, RIGHT + } + @WritingConverter enum StringToBigDecimalConverter implements Converter { @@ -214,4 +262,55 @@ public CustomId convert(Number source) { } } + @WritingConverter + enum DirectionToIntegerConverter implements Converter { + + INSTANCE; + + @Override + public JdbcValue convert(Direction source) { + + int integer = switch (source) { + case LEFT -> -1; + case CENTER -> 0; + case RIGHT -> 1; + }; + return JdbcValue.of(integer, JDBCType.INTEGER); + } + } + + @ReadingConverter // Needed for Oracle since the JDBC driver returns BigDecimal on read + enum NumberToDirectionConverter implements Converter { + + 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 { + + INSTANCE; + + @Override + public Direction convert(Integer source) { + if (source == 0) { + return Direction.CENTER; + } else if (source < 0) { + return Direction.LEFT; + } else { + return Direction.RIGHT; + } + } + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 85f9e1e1f7..f2c1a6810b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -20,10 +20,6 @@ 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.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; @@ -50,7 +47,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jdbc.core.mapping.AggregateReference; -import org.springframework.data.relational.repository.Lock; import org.springframework.data.jdbc.repository.query.Modifying; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; @@ -62,6 +58,7 @@ import org.springframework.data.relational.core.mapping.event.AfterConvertEvent; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; import org.springframework.data.relational.core.sql.LockMode; +import org.springframework.data.relational.repository.Lock; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; @@ -75,11 +72,16 @@ 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 @@ void nullStringResult() { 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(Set.of(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 @@ interface DummyEntityRepository extends CrudRepository { @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 findByEnumTypeIn(Set directions); + + @Query("SELECT * FROM DUMMY_ENTITY WHERE DIRECTION = :direction") + List findByEnumType(Direction direction); } @Configuration @@ -713,12 +753,17 @@ static class DummyEntity { @Id private Long idProp; boolean flag; AggregateReference ref; + Direction direction; public DummyEntity(String name) { this.name = name; } } + enum Direction { + LEFT, CENTER, RIGHT + } + interface DummyProjection { String getName(); 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 6e3a5d96bd..b2f844af64 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 @@ -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,24 +19,33 @@ 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 +64,7 @@ * @author Evgeni Dimitrov * @author Mark Paluch * @author Dennis Effing + * @author Chirag Tailor */ class StringBasedJdbcQueryUnitTests { @@ -64,7 +74,7 @@ class StringBasedJdbcQueryUnitTests { JdbcConverter converter; @BeforeEach - void setup() throws NoSuchMethodException { + void setup() { this.defaultRowMapper = mock(RowMapper.class); this.operations = mock(NamedParameterJdbcOperations.class); @@ -172,6 +182,54 @@ void pageQueryNotSupported() { .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[] { 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); + + 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(-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 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 +270,11 @@ interface MyRepository extends Repository { @Query(value = "some sql statement") Slice sliceAll(Pageable pageable); + @Query(value = "some sql statement") + List findByEnumTypeIn(Set directions); + + @Query(value = "some sql statement") + List findBySimpleValue(Integer value); } private static class CustomRowMapper implements RowMapper { @@ -241,6 +304,45 @@ public Object extractData(ResultSet rs) throws DataAccessException { } } + private enum Direction { + LEFT, CENTER, RIGHT + } + + @WritingConverter + enum DirectionToIntegerConverter implements Converter { + + INSTANCE; + + @Override + public JdbcValue convert(Direction source) { + + int integer = switch (source) { + case LEFT -> -1; + case CENTER -> 0; + case RIGHT -> 1; + }; + return JdbcValue.of(integer, JDBCType.INTEGER); + } + } + + @ReadingConverter + enum IntegerToDirectionConverter implements Converter { + + 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; diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-db2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-db2.sql index e75592d0bc..6abce10e3a 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-db2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-db2.sql @@ -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); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql index d383545694..426153b9e3 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-h2.sql @@ -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); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql index 78d9c930b7..9508fbb0e2 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-hsql.sql @@ -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); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql index e89bb0d951..4e2dee5382 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mariadb.sql @@ -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); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql index 34b2982692..0a884be3cf 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mssql.sql @@ -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); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql index 34776fc5a8..b1d3da76c2 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-mysql.sql @@ -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); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-oracle.sql index 1b02ef7214..1d653cf453 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-oracle.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-oracle.sql @@ -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 ( diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql index c376dcf03f..882d8df894 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-postgres.sql @@ -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); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql index 34be74ec51..ce25ff6442 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql @@ -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) ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql index b3b93bc744..30a8df2e20 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql @@ -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) ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql index b3b93bc744..30a8df2e20 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql @@ -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) ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql index 949e626399..c505db351c 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql @@ -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) ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql index 15f8881327..94f7aadf9f 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql @@ -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) ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql index e3baa94602..eb04d9ed4d 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql @@ -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) ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql index e71eb63286..c575139be1 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql @@ -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) ); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql index 97fc78c9da..592b3ea26e 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql @@ -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) ); 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 dd719294c1..cd8ad128de 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 @@ -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; @@ -57,9 +59,12 @@ protected RelationalParameters createFrom(List parameters) * 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}. * @@ -67,6 +72,13 @@ public static class RelationalParameter extends Parameter { */ RelationalParameter(MethodParameter parameter) { super(parameter); + this.parameter = parameter; + } + + + public ResolvableType getResolvableType() { + return ResolvableType + .forClassWithGenerics(super.getType(), ResolvableType.forMethodParameter(this.parameter).getGenerics()); } } }