Skip to content

Commit 11734c1

Browse files
schaudermp911de
authored andcommitted
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.
1 parent 4f4014d commit 11734c1

17 files changed

+248
-65
lines changed

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConverters.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor,
7878
return null;
7979
}
8080

81-
// if the target type is an AggregateReference we just going to assume it is of the correct type,
81+
// if the target type is an AggregateReference we are going to assume it is of the correct type,
8282
// because it was already converted.
8383
Class<?> objectType = targetDescriptor.getObjectType();
8484
if (objectType.isAssignableFrom(AggregateReference.class)) {

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ public Object readValue(@Nullable Object value, TypeInformation<?> type) {
232232
targetDescriptor);
233233
}
234234

235-
if (value instanceof Array) {
235+
if ( !getConversions().hasCustomReadTarget(value.getClass(), type.getType()) &&
236+
value instanceof Array) {
236237
try {
237238
return readValue(((Array) value).getArray(), type);
238239
} catch (SQLException | ConverterNotFoundException e) {

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Collections;
2121
import java.util.List;
2222

23+
import org.springframework.core.convert.ConversionService;
2324
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
2425
import org.springframework.core.convert.support.DefaultConversionService;
2526
import org.springframework.data.convert.CustomConversions;
@@ -50,6 +51,7 @@ public class JdbcCustomConversions extends CustomConversions {
5051
STORE_CONVERTERS = Collections.unmodifiableCollection(converters);
5152

5253
}
54+
5355
private static final StoreConversions STORE_CONVERSIONS = StoreConversions.of(JdbcSimpleTypes.HOLDER,
5456
STORE_CONVERTERS);
5557

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java

-5
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,6 @@ private static void validateProperty(PersistentPropertyPathExtension path) {
146146
throw new IllegalArgumentException(
147147
String.format("Cannot query by nested entity: %s", path.getRequiredPersistentPropertyPath().toDotPath()));
148148
}
149-
150-
if (path.getRequiredPersistentPropertyPath().getLeafProperty().isReference()) {
151-
throw new IllegalArgumentException(
152-
String.format("Cannot query by reference: %s", path.getRequiredPersistentPropertyPath().toDotPath()));
153-
}
154149
}
155150

156151
/**

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/QueryMapper.java

+92-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.jdbc.repository.query;
1717

18+
import java.sql.JDBCType;
1819
import java.sql.Types;
1920
import java.util.ArrayList;
2021
import java.util.Collection;
@@ -245,8 +246,8 @@ private Condition getCondition(CriteriaDefinition criteria, MapSqlParameterSourc
245246
return mapCondition(criteria, parameterSource, table, entity);
246247
}
247248

248-
private Condition combine(@Nullable Condition currentCondition,
249-
CriteriaDefinition.Combinator combinator, Condition nextCondition) {
249+
private Condition combine(@Nullable Condition currentCondition, CriteriaDefinition.Combinator combinator,
250+
Condition nextCondition) {
250251

251252
if (currentCondition == null) {
252253
currentCondition = nextCondition;
@@ -292,6 +293,17 @@ private Condition mapCondition(CriteriaDefinition criteria, MapSqlParameterSourc
292293

293294
mappedValue = convertValue(value, propertyField.getTypeHint());
294295
sqlType = propertyField.getSqlType();
296+
297+
} else if (propertyField instanceof MetadataBackedField //
298+
&& ((MetadataBackedField) propertyField).property != null //
299+
&& (criteria.getValue() == null || !criteria.getValue().getClass().isArray())) {
300+
301+
RelationalPersistentProperty property = ((MetadataBackedField) propertyField).property;
302+
JdbcValue jdbcValue = convertSpecial(property, criteria.getValue());
303+
mappedValue = jdbcValue.getValue();
304+
sqlType = jdbcValue.getJdbcType() != null ? jdbcValue.getJdbcType().getVendorTypeNumber()
305+
: propertyField.getSqlType();
306+
295307
} else {
296308

297309
mappedValue = convertValue(criteria.getValue(), propertyField.getTypeHint());
@@ -302,6 +314,84 @@ private Condition mapCondition(CriteriaDefinition criteria, MapSqlParameterSourc
302314
criteria.isIgnoreCase());
303315
}
304316

317+
/**
318+
* Converts values while taking special value types like arrays, {@link Iterable}, or {@link Pair}.
319+
*
320+
* @param property the property to which the value relates. It determines the type to convert to. Must not be
321+
* {@literal null}.
322+
* @param value the value to be converted.
323+
* @return a non null {@link JdbcValue} holding the converted value and the appropriate JDBC type information.
324+
*/
325+
private JdbcValue convertSpecial(RelationalPersistentProperty property, @Nullable Object value) {
326+
327+
if (value == null) {
328+
return JdbcValue.of(null, JDBCType.NULL);
329+
}
330+
331+
if (value instanceof Pair) {
332+
333+
final JdbcValue first = convertSimple(property, ((Pair<?, ?>) value).getFirst());
334+
final JdbcValue second = convertSimple(property, ((Pair<?, ?>) value).getSecond());
335+
return JdbcValue.of(Pair.of(first.getValue(), second.getValue()), first.getJdbcType());
336+
}
337+
338+
if (value instanceof Iterable) {
339+
340+
List<Object> mapped = new ArrayList<>();
341+
JDBCType jdbcType = null;
342+
343+
for (Object o : (Iterable<?>) value) {
344+
345+
final JdbcValue jdbcValue = convertSimple(property, o);
346+
if (jdbcType == null) {
347+
jdbcType = jdbcValue.getJdbcType();
348+
}
349+
350+
mapped.add(jdbcValue.getValue());
351+
}
352+
353+
return JdbcValue.of(mapped, jdbcType);
354+
}
355+
356+
if (value.getClass().isArray()) {
357+
358+
final Object[] valueAsArray = (Object[]) value;
359+
final Object[] mappedValueArray = new Object[valueAsArray.length];
360+
JDBCType jdbcType = null;
361+
362+
for (int i = 0; i < valueAsArray.length; i++) {
363+
364+
final JdbcValue jdbcValue = convertSimple(property, valueAsArray[i]);
365+
if (jdbcType == null) {
366+
jdbcType = jdbcValue.getJdbcType();
367+
}
368+
369+
mappedValueArray[i] = jdbcValue.getValue();
370+
}
371+
372+
return JdbcValue.of(mappedValueArray, jdbcType);
373+
}
374+
375+
return convertSimple(property, value);
376+
}
377+
378+
/**
379+
* Converts values to a {@link JdbcValue}.
380+
*
381+
* @param property the property to which the value relates. It determines the type to convert to. Must not be
382+
* {@literal null}.
383+
* @param value the value to be converted.
384+
* @return a non null {@link JdbcValue} holding the converted value and the appropriate JDBC type information.
385+
*/
386+
private JdbcValue convertSimple(RelationalPersistentProperty property, Object value) {
387+
388+
return converter.writeJdbcValue( //
389+
value, //
390+
converter.getColumnType(property), //
391+
converter.getSqlType(property) //
392+
);
393+
}
394+
305395
private Condition mapEmbeddedObjectCondition(CriteriaDefinition criteria, MapSqlParameterSource parameterSource,
306396
Table table, RelationalPersistentProperty embeddedProperty) {
307397

@@ -740,11 +830,6 @@ public TypeInformation<?> getTypeHint() {
740830
return this.property.getTypeInformation();
741831
}
742832

743-
if (this.property.getType().isInterface()
744-
|| (java.lang.reflect.Modifier.isAbstract(this.property.getType().getModifiers()))) {
745-
return ClassTypeInformation.OBJECT;
746-
}
747-
748833
return this.property.getTypeInformation();
749834
}
750835

Diff for: spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/AggregateReferenceConvertersUnitTests.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ void setUp() {
4646
@Test // GH-992
4747
void convertsFromSimpleValue() {
4848

49-
ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class, String.class, Integer.class);
49+
ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class,
50+
String.class, Integer.class);
5051
Object converted = conversionService.convert(23, TypeDescriptor.forObject(23),
5152
new TypeDescriptor(aggregateReferenceWithIdTypeInteger, null, null));
5253

@@ -56,7 +57,8 @@ void convertsFromSimpleValue() {
5657
@Test // GH-992
5758
void convertsFromSimpleValueThatNeedsSeparateConversion() {
5859

59-
ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class, String.class, Long.class);
60+
ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class,
61+
String.class, Long.class);
6062
Object converted = conversionService.convert(23, TypeDescriptor.forObject(23),
6163
new TypeDescriptor(aggregateReferenceWithIdTypeInteger, null, null));
6264

Diff for: spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

+31
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.data.domain.PageRequest;
5151
import org.springframework.data.domain.Pageable;
5252
import org.springframework.data.domain.Slice;
53+
import org.springframework.data.jdbc.core.mapping.AggregateReference;
5354
import org.springframework.data.jdbc.repository.query.Modifying;
5455
import org.springframework.data.jdbc.repository.query.Query;
5556
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
@@ -514,6 +515,32 @@ void derivedQueryWithBooleanLiteralFindsCorrectValues() {
514515
assertThat(result).extracting(e -> e.idProp).containsExactly(entity.idProp);
515516
}
516517

518+
@Test // #987
519+
void queryBySimpleReference() {
520+
521+
final DummyEntity one = repository.save(createDummyEntity());
522+
DummyEntity two = createDummyEntity();
523+
two.ref = AggregateReference.to(one.idProp);
524+
two = repository.save(two);
525+
526+
List<DummyEntity> result = repository.findByRef(one.idProp.intValue());
527+
528+
assertThat(result).extracting(e -> e.idProp).containsExactly(two.idProp);
529+
}
530+
531+
@Test // #987
532+
void queryByAggregateReference() {
533+
534+
final DummyEntity one = repository.save(createDummyEntity());
535+
DummyEntity two = createDummyEntity();
536+
two.ref = AggregateReference.to(one.idProp);
537+
two = repository.save(two);
538+
539+
List<DummyEntity> result = repository.findByRef(two.ref);
540+
541+
assertThat(result).extracting(e -> e.idProp).containsExactly(two.idProp);
542+
}
543+
517544
private Instant createDummyBeforeAndAfterNow() {
518545

519546
Instant now = Instant.now();
@@ -585,6 +612,9 @@ interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
585612
void updateWithIntervalCalculation(@Param("id") Long id, @Param("start") LocalDateTime start);
586613

587614
List<DummyEntity> findByFlagTrue();
615+
616+
List<DummyEntity> findByRef(int ref);
617+
List<DummyEntity> findByRef(AggregateReference<DummyEntity, Long> ref);
588618
}
589619

590620
@Configuration
@@ -637,6 +667,7 @@ static class DummyEntity {
637667
OffsetDateTime offsetDateTime;
638668
@Id private Long idProp;
639669
boolean flag;
670+
AggregateReference<DummyEntity, Long> ref;
640671

641672
public DummyEntity(String name) {
642673
this.name = name;

0 commit comments

Comments
 (0)