From 11734c1bcc08169c60f2cf765aa1aaabdb87e2f3 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 2 Jun 2021 14:05:44 +0200 Subject: [PATCH] Support AggregateReference in query derivation. For a property of type AggregateReference one may provide an aggregate, an AggregateReference, or the id of the aggregate. Closes #987 Original pull request: #999. --- .../convert/AggregateReferenceConverters.java | 2 +- .../jdbc/core/convert/BasicJdbcConverter.java | 3 +- .../core/convert/JdbcCustomConversions.java | 2 + .../repository/query/JdbcQueryCreator.java | 5 - .../jdbc/repository/query/QueryMapper.java | 99 +++++++++++++++++-- ...AggregateReferenceConvertersUnitTests.java | 6 +- .../JdbcRepositoryIntegrationTests.java | 31 ++++++ .../query/PartTreeJdbcQueryUnitTests.java | 75 +++++++++++--- .../JdbcRepositoryIntegrationTests-db2.sql | 9 +- .../JdbcRepositoryIntegrationTests-h2.sql | 9 +- .../JdbcRepositoryIntegrationTests-hsql.sql | 9 +- ...JdbcRepositoryIntegrationTests-mariadb.sql | 9 +- .../JdbcRepositoryIntegrationTests-mssql.sql | 9 +- .../JdbcRepositoryIntegrationTests-mysql.sql | 12 ++- .../JdbcRepositoryIntegrationTests-oracle.sql | 9 +- ...dbcRepositoryIntegrationTests-postgres.sql | 9 +- .../conversion/BasicRelationalConverter.java | 15 ++- 17 files changed, 248 insertions(+), 65 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConverters.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConverters.java index f3d917292..c805230d9 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConverters.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConverters.java @@ -78,7 +78,7 @@ class AggregateReferenceConverters { return null; } - // if the target type is an AggregateReference we just going to assume it is of the correct type, + // if the target type is an AggregateReference we are going to assume it is of the correct type, // because it was already converted. Class objectType = targetDescriptor.getObjectType(); if (objectType.isAssignableFrom(AggregateReference.class)) { 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 2ac78871a..f8363ded5 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 @@ -232,7 +232,8 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc targetDescriptor); } - if (value instanceof Array) { + if ( !getConversions().hasCustomReadTarget(value.getClass(), type.getType()) && + value instanceof Array) { try { return readValue(((Array) value).getArray(), type); } catch (SQLException | ConverterNotFoundException e) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java index 9107db8c5..c2f11999f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.convert.CustomConversions; @@ -50,6 +51,7 @@ public class JdbcCustomConversions extends CustomConversions { STORE_CONVERTERS = Collections.unmodifiableCollection(converters); } + private static final StoreConversions STORE_CONVERSIONS = StoreConversions.of(JdbcSimpleTypes.HOLDER, STORE_CONVERTERS); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java index 5f6105a17..d86549f14 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java @@ -146,11 +146,6 @@ class JdbcQueryCreator extends RelationalQueryCreator { throw new IllegalArgumentException( String.format("Cannot query by nested entity: %s", path.getRequiredPersistentPropertyPath().toDotPath())); } - - if (path.getRequiredPersistentPropertyPath().getLeafProperty().isReference()) { - throw new IllegalArgumentException( - String.format("Cannot query by reference: %s", path.getRequiredPersistentPropertyPath().toDotPath())); - } } /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java index 328e08829..fde30f9b7 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jdbc.repository.query; +import java.sql.JDBCType; import java.sql.Types; import java.util.ArrayList; import java.util.Collection; @@ -245,8 +246,8 @@ class QueryMapper { return mapCondition(criteria, parameterSource, table, entity); } - private Condition combine(@Nullable Condition currentCondition, - CriteriaDefinition.Combinator combinator, Condition nextCondition) { + private Condition combine(@Nullable Condition currentCondition, CriteriaDefinition.Combinator combinator, + Condition nextCondition) { if (currentCondition == null) { currentCondition = nextCondition; @@ -292,6 +293,17 @@ class QueryMapper { mappedValue = convertValue(value, propertyField.getTypeHint()); sqlType = propertyField.getSqlType(); + + } else if (propertyField instanceof MetadataBackedField // + && ((MetadataBackedField) propertyField).property != null // + && (criteria.getValue() == null || !criteria.getValue().getClass().isArray())) { + + RelationalPersistentProperty property = ((MetadataBackedField) propertyField).property; + JdbcValue jdbcValue = convertSpecial(property, criteria.getValue()); + mappedValue = jdbcValue.getValue(); + sqlType = jdbcValue.getJdbcType() != null ? jdbcValue.getJdbcType().getVendorTypeNumber() + : propertyField.getSqlType(); + } else { mappedValue = convertValue(criteria.getValue(), propertyField.getTypeHint()); @@ -302,6 +314,84 @@ class QueryMapper { criteria.isIgnoreCase()); } + /** + * Converts values while taking special value types like arrays, {@link Iterable}, or {@link Pair}. + * + * @param property the property to which the value relates. It determines the type to convert to. Must not be + * {@literal null}. + * @param value the value to be converted. + * @return a non null {@link JdbcValue} holding the converted value and the appropriate JDBC type information. + */ + private JdbcValue convertSpecial(RelationalPersistentProperty property, @Nullable Object value) { + + if (value == null) { + return JdbcValue.of(null, JDBCType.NULL); + } + + if (value instanceof Pair) { + + final JdbcValue first = convertSimple(property, ((Pair) value).getFirst()); + final JdbcValue second = convertSimple(property, ((Pair) value).getSecond()); + return JdbcValue.of(Pair.of(first.getValue(), second.getValue()), first.getJdbcType()); + } + + if (value instanceof Iterable) { + + List mapped = new ArrayList<>(); + JDBCType jdbcType = null; + + for (Object o : (Iterable) value) { + + final JdbcValue jdbcValue = convertSimple(property, o); + if (jdbcType == null) { + jdbcType = jdbcValue.getJdbcType(); + } + + mapped.add(jdbcValue.getValue()); + } + + return JdbcValue.of(mapped, jdbcType); + } + + if (value.getClass().isArray()) { + + final Object[] valueAsArray = (Object[]) value; + final Object[] mappedValueArray = new Object[valueAsArray.length]; + JDBCType jdbcType = null; + + for (int i = 0; i < valueAsArray.length; i++) { + + final JdbcValue jdbcValue = convertSimple(property, valueAsArray[i]); + if (jdbcType == null) { + jdbcType = jdbcValue.getJdbcType(); + } + + mappedValueArray[i] = jdbcValue.getValue(); + } + + return JdbcValue.of(mappedValueArray, jdbcType); + } + + return convertSimple(property, value); + } + + /** + * Converts values to a {@link JdbcValue}. + * + * @param property the property to which the value relates. It determines the type to convert to. Must not be + * {@literal null}. + * @param value the value to be converted. + * @return a non null {@link JdbcValue} holding the converted value and the appropriate JDBC type information. + */ + private JdbcValue convertSimple(RelationalPersistentProperty property, Object value) { + + return converter.writeJdbcValue( // + value, // + converter.getColumnType(property), // + converter.getSqlType(property) // + ); + } + private Condition mapEmbeddedObjectCondition(CriteriaDefinition criteria, MapSqlParameterSource parameterSource, Table table, RelationalPersistentProperty embeddedProperty) { @@ -740,11 +830,6 @@ class QueryMapper { return this.property.getTypeInformation(); } - if (this.property.getType().isInterface() - || (java.lang.reflect.Modifier.isAbstract(this.property.getType().getModifiers()))) { - return ClassTypeInformation.OBJECT; - } - return this.property.getTypeInformation(); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConvertersUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConvertersUnitTests.java index 9bfd9a757..f0a227a9e 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConvertersUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConvertersUnitTests.java @@ -46,7 +46,8 @@ class AggregateReferenceConvertersUnitTests { @Test // GH-992 void convertsFromSimpleValue() { - ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class, String.class, Integer.class); + ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class, + String.class, Integer.class); Object converted = conversionService.convert(23, TypeDescriptor.forObject(23), new TypeDescriptor(aggregateReferenceWithIdTypeInteger, null, null)); @@ -56,7 +57,8 @@ class AggregateReferenceConvertersUnitTests { @Test // GH-992 void convertsFromSimpleValueThatNeedsSeparateConversion() { - ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class, String.class, Long.class); + ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class, + String.class, Long.class); Object converted = conversionService.convert(23, TypeDescriptor.forObject(23), new TypeDescriptor(aggregateReferenceWithIdTypeInteger, null, null)); 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 c4dd47552..7a3214659 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 @@ -50,6 +50,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.repository.query.Modifying; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; @@ -514,6 +515,32 @@ public class JdbcRepositoryIntegrationTests { assertThat(result).extracting(e -> e.idProp).containsExactly(entity.idProp); } + @Test // #987 + void queryBySimpleReference() { + + final DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = createDummyEntity(); + two.ref = AggregateReference.to(one.idProp); + two = repository.save(two); + + List result = repository.findByRef(one.idProp.intValue()); + + assertThat(result).extracting(e -> e.idProp).containsExactly(two.idProp); + } + + @Test // #987 + void queryByAggregateReference() { + + final DummyEntity one = repository.save(createDummyEntity()); + DummyEntity two = createDummyEntity(); + two.ref = AggregateReference.to(one.idProp); + two = repository.save(two); + + List result = repository.findByRef(two.ref); + + assertThat(result).extracting(e -> e.idProp).containsExactly(two.idProp); + } + private Instant createDummyBeforeAndAfterNow() { Instant now = Instant.now(); @@ -585,6 +612,9 @@ public class JdbcRepositoryIntegrationTests { void updateWithIntervalCalculation(@Param("id") Long id, @Param("start") LocalDateTime start); List findByFlagTrue(); + + List findByRef(int ref); + List findByRef(AggregateReference ref); } @Configuration @@ -637,6 +667,7 @@ public class JdbcRepositoryIntegrationTests { OffsetDateTime offsetDateTime; @Id private Long idProp; boolean flag; + AggregateReference ref; public DummyEntity(String name) { this.name = name; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java index 93e318447..0e53914af 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.repository.query; import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.SoftAssertions.*; import static org.mockito.Mockito.*; import lombok.AllArgsConstructor; @@ -27,11 +28,9 @@ import java.util.Date; import java.util.List; import java.util.Properties; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; - import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -64,7 +63,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; public class PartTreeJdbcQueryUnitTests { private static final String TABLE = "\"users\""; - private static final String ALL_FIELDS = "\"users\".\"ID\" AS \"ID\", \"users\".\"AGE\" AS \"AGE\", \"hated\".\"USER\" AS \"HATED_USER\", \"users\".\"ACTIVE\" AS \"ACTIVE\", \"users\".\"LAST_NAME\" AS \"LAST_NAME\", \"users\".\"FIRST_NAME\" AS \"FIRST_NAME\", \"users\".\"DATE_OF_BIRTH\" AS \"DATE_OF_BIRTH\", \"users\".\"HOBBY_REFERENCE\" AS \"HOBBY_REFERENCE\", \"hated\".\"NAME\" AS \"HATED_NAME\", \"users\".\"USER_CITY\" AS \"USER_CITY\", \"users\".\"USER_STREET\" AS \"USER_STREET\""; + private static final String ALL_FIELDS = "\"users\".\"ID\" AS \"ID\", \"users\".\"AGE\" AS \"AGE\", \"users\".\"ACTIVE\" AS \"ACTIVE\", \"users\".\"LAST_NAME\" AS \"LAST_NAME\", \"users\".\"FIRST_NAME\" AS \"FIRST_NAME\", \"users\".\"DATE_OF_BIRTH\" AS \"DATE_OF_BIRTH\", \"users\".\"HOBBY_REFERENCE\" AS \"HOBBY_REFERENCE\", \"hated\".\"NAME\" AS \"HATED_NAME\", \"users\".\"USER_CITY\" AS \"USER_CITY\", \"users\".\"USER_STREET\" AS \"USER_STREET\""; private static final String JOIN_CLAUSE = "FROM \"users\" LEFT OUTER JOIN \"HOBBY\" \"hated\" ON \"hated\".\"USER\" = \"users\".\"ID\""; private static final String BASE_SELECT = "SELECT " + ALL_FIELDS + " " + JOIN_CLAUSE; @@ -79,11 +78,22 @@ public class PartTreeJdbcQueryUnitTests { assertThatIllegalArgumentException().isThrownBy(() -> createQuery(queryMethod)); } - @Test // DATAJDBC-318 - public void shouldFailForQueryByAggregateReference() throws Exception { + @Test // #922 + public void createQueryByAggregateReference() throws Exception { JdbcQueryMethod queryMethod = getQueryMethod("findAllByHobbyReference", Hobby.class); - assertThatIllegalArgumentException().isThrownBy(() -> createQuery(queryMethod)); + PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod); + final Hobby hobby = new Hobby(); + hobby.name = "twentythree"; + ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] {hobby}), returnedType); + + assertSoftly(softly -> { + + softly.assertThat(query.getQuery()) + .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"HOBBY_REFERENCE\" = :hobby_reference"); + + softly.assertThat(query.getParameterSource().getValue("hobby_reference")).isEqualTo("twentythree"); + }); } @Test // DATAJDBC-318 @@ -100,11 +110,38 @@ public class PartTreeJdbcQueryUnitTests { assertThatIllegalArgumentException().isThrownBy(() -> createQuery(queryMethod)); } - @Test // DATAJDBC-318 - public void shouldFailForAggregateReference() throws Exception { + @Test // #922 + public void createQueryForQueryByAggregateReference() throws Exception { - JdbcQueryMethod queryMethod = getQueryMethod("findByAnotherEmbeddedList", Object.class); - assertThatIllegalArgumentException().isThrownBy(() -> createQuery(queryMethod)); + JdbcQueryMethod queryMethod = getQueryMethod("findViaReferenceByHobbyReference", AggregateReference.class); + PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod); + final AggregateReference hobby = AggregateReference.to("twentythree"); + ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] {hobby}), returnedType); + + assertSoftly(softly -> { + + softly.assertThat(query.getQuery()) + .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"HOBBY_REFERENCE\" = :hobby_reference"); + + softly.assertThat(query.getParameterSource().getValue("hobby_reference")).isEqualTo("twentythree"); + }); + } + + @Test // #922 + public void createQueryForQueryByAggregateReferenceId() throws Exception { + + JdbcQueryMethod queryMethod = getQueryMethod("findViaIdByHobbyReference", String.class); + PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod); + final String hobby = "twentythree"; + ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] {hobby}), returnedType); + + assertSoftly(softly -> { + + softly.assertThat(query.getQuery()) + .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"HOBBY_REFERENCE\" = :hobby_reference"); + + softly.assertThat(query.getParameterSource().getValue("hobby_reference")).isEqualTo("twentythree"); + }); } @Test // DATAJDBC-318 @@ -176,6 +213,7 @@ public class PartTreeJdbcQueryUnitTests { + ".\"FIRST_NAME\" = :first_name)"); } + @Test // DATAJDBC-318 public void createsQueryToFindAllEntitiesByDateAttributeBetween() throws Exception { @@ -186,11 +224,14 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { from, to }); ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType); - assertThat(query.getQuery()) - .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" BETWEEN :date_of_birth AND :date_of_birth1"); + assertSoftly(softly -> { + + softly.assertThat(query.getQuery()) + .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" BETWEEN :date_of_birth AND :date_of_birth1"); - assertThat(query.getParameterSource().getValue("date_of_birth")).isEqualTo(from); - assertThat(query.getParameterSource().getValue("date_of_birth1")).isEqualTo(to); + softly.assertThat(query.getParameterSource().getValue("date_of_birth")).isEqualTo(from); + softly.assertThat(query.getParameterSource().getValue("date_of_birth1")).isEqualTo(to); + }); } @Test // DATAJDBC-318 @@ -250,6 +291,7 @@ public class PartTreeJdbcQueryUnitTests { @Test // DATAJDBC-318 public void createsQueryToFindAllEntitiesByDateAttributeBefore() throws Exception { + JdbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthBefore", Date.class); PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() }); @@ -608,6 +650,10 @@ public class PartTreeJdbcQueryUnitTests { List findAllByHobbyReference(Hobby hobby); + List findViaReferenceByHobbyReference(AggregateReference hobby); + + List findViaIdByHobbyReference(String hobby); + List findAllByLastNameAndFirstName(String lastName, String firstName); List findAllByLastNameOrFirstName(String lastName, String firstName); @@ -708,6 +754,7 @@ public class PartTreeJdbcQueryUnitTests { } static class Hobby { + @Id String name; } } 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 d41d8accd..34be74ec5 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 @@ -2,9 +2,10 @@ DROP TABLE dummy_entity; CREATE TABLE dummy_entity ( - id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, - NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP, + id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + NAME VARCHAR(100), + POINT_IN_TIME TIMESTAMP, OFFSET_DATE_TIME TIMESTAMP, -- with time zone is only supported with z/OS - FLAG BOOLEAN + FLAG BOOLEAN, + REF BIGINT ); 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 5a3f1654a..b3b93bc74 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 @@ -1,8 +1,9 @@ CREATE TABLE dummy_entity ( - id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, - NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP, + id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + NAME VARCHAR(100), + POINT_IN_TIME TIMESTAMP, OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE, - FLAG BOOLEAN + FLAG BOOLEAN, + REF BIGINT ); 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 5a3f1654a..b3b93bc74 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 @@ -1,8 +1,9 @@ CREATE TABLE dummy_entity ( - id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, - NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP, + id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + NAME VARCHAR(100), + POINT_IN_TIME TIMESTAMP, OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE, - FLAG BOOLEAN + FLAG BOOLEAN, + REF BIGINT ); 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 663446bdc..949e62639 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 @@ -1,8 +1,9 @@ CREATE TABLE dummy_entity ( - id_Prop BIGINT AUTO_INCREMENT PRIMARY KEY, - NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP(3), + id_Prop BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(100), + POINT_IN_TIME TIMESTAMP(3), OFFSET_DATE_TIME TIMESTAMP(3), - FLAG BOOLEAN + FLAG BOOLEAN, + REF BIGINT ); 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 f18b9da5c..15f888132 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 @@ -1,9 +1,10 @@ DROP TABLE IF EXISTS dummy_entity; CREATE TABLE dummy_entity ( - id_Prop BIGINT IDENTITY PRIMARY KEY, - NAME VARCHAR(100), - POINT_IN_TIME DATETIME, + id_Prop BIGINT IDENTITY PRIMARY KEY, + NAME VARCHAR(100), + POINT_IN_TIME DATETIME, OFFSET_DATE_TIME DATETIMEOFFSET, - FLAG BIT + FLAG BIT, + REF BIGINT ); 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 60e23ca6b..e3baa9460 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 @@ -1,10 +1,12 @@ -SET SQL_MODE='ALLOW_INVALID_DATES'; +SET +SQL_MODE='ALLOW_INVALID_DATES'; CREATE TABLE DUMMY_ENTITY ( - ID_PROP BIGINT AUTO_INCREMENT PRIMARY KEY, - NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP(3) DEFAULT NULL, + ID_PROP BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(100), + POINT_IN_TIME TIMESTAMP(3) DEFAULT NULL, OFFSET_DATE_TIME TIMESTAMP(3) DEFAULT NULL, - FLAG BIT(1) + FLAG BIT(1), + REF BIGINT ); 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 5a92e2a23..e71eb6328 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 @@ -2,9 +2,10 @@ DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE; CREATE TABLE DUMMY_ENTITY ( - ID_PROP NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, - NAME VARCHAR2(100), - POINT_IN_TIME TIMESTAMP, + ID_PROP NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + NAME VARCHAR2(100), + POINT_IN_TIME TIMESTAMP, OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE, - FLAG NUMBER(1,0) + FLAG NUMBER(1,0), + REF NUMBER ); 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 05f4908e7..97fc78c9d 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 @@ -1,9 +1,10 @@ DROP TABLE dummy_entity; CREATE TABLE dummy_entity ( - id_Prop SERIAL PRIMARY KEY, - NAME VARCHAR(100), - POINT_IN_TIME TIMESTAMP, + id_Prop SERIAL PRIMARY KEY, + NAME VARCHAR(100), + POINT_IN_TIME TIMESTAMP, OFFSET_DATE_TIME TIMESTAMP WITH TIME ZONE, - FLAG BOOLEAN + FLAG BOOLEAN, + REF BIGINT ); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java index fad388e71..6bcf79a07 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java @@ -157,8 +157,12 @@ public class BasicRelationalConverter implements RelationalConverter { return null; } - if (conversions.hasCustomReadTarget(value.getClass(), type.getType())) { - return conversionService.convert(value, type.getType()); + if (getConversions().hasCustomReadTarget(value.getClass(), type.getType())) { + + TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(value.getClass()); + TypeDescriptor targetDescriptor = typeInformationToTypeDescriptor(type); + + return getConversionService().convert(value, sourceDescriptor, targetDescriptor); } return getPotentiallyConvertedSimpleRead(value, type.getType()); @@ -246,6 +250,13 @@ public class BasicRelationalConverter implements RelationalConverter { return conversionService.convert(value, target); } + protected static TypeDescriptor typeInformationToTypeDescriptor(TypeInformation type) { + + Class[] generics = type.getTypeArguments().stream().map(TypeInformation::getType).toArray(Class[]::new); + + return new TypeDescriptor(ResolvableType.forClassWithGenerics(type.getType(), generics), null, null); + } + /** * Converter-aware {@link ParameterValueProvider}. *