Skip to content

Commit 8ad3136

Browse files
committedJul 2, 2024··
Fix regression of loading empty arrays.
Closes #1826 See #1812
1 parent fea01a5 commit 8ad3136

File tree

3 files changed

+108
-13
lines changed

3 files changed

+108
-13
lines changed
 

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

+37-12
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
import org.apache.commons.logging.Log;
2828
import org.apache.commons.logging.LogFactory;
29-
3029
import org.springframework.context.ApplicationContextAware;
3130
import org.springframework.core.convert.ConverterNotFoundException;
3231
import org.springframework.core.convert.converter.Converter;
@@ -81,7 +80,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
8180
* {@link #MappingJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory)}
8281
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
8382
*
84-
* @param context must not be {@literal null}.
83+
* @param context must not be {@literal null}.
8584
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
8685
*/
8786
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {
@@ -99,12 +98,12 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r
9998
/**
10099
* Creates a new {@link MappingJdbcConverter} given {@link MappingContext}.
101100
*
102-
* @param context must not be {@literal null}.
101+
* @param context must not be {@literal null}.
103102
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
104-
* @param typeFactory must not be {@literal null}
103+
* @param typeFactory must not be {@literal null}
105104
*/
106105
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
107-
CustomConversions conversions, JdbcTypeFactory typeFactory) {
106+
CustomConversions conversions, JdbcTypeFactory typeFactory) {
108107

109108
super(context, conversions);
110109

@@ -301,7 +300,7 @@ public <R> R readAndResolve(TypeInformation<R> type, RowDocument source, Identif
301300

302301
@Override
303302
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
304-
ValueExpressionEvaluator evaluator, ConversionContext context) {
303+
ValueExpressionEvaluator evaluator, ConversionContext context) {
305304

306305
if (context instanceof ResolvingConversionContext rcc) {
307306

@@ -330,7 +329,7 @@ class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValu
330329
private final Identifier identifier;
331330

332331
private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor,
333-
ResolvingConversionContext context, Identifier identifier) {
332+
ResolvingConversionContext context, Identifier identifier) {
334333

335334
AggregatePath path = context.aggregatePath();
336335

@@ -339,15 +338,15 @@ private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider dele
339338
this.context = context;
340339
this.identifier = path.isEntity()
341340
? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(),
342-
property -> delegate.getValue(path.append(property)))
341+
property -> delegate.getValue(path.append(property)))
343342
: identifier;
344343
}
345344

346345
/**
347346
* Conditionally append the identifier if the entity has an identifier property.
348347
*/
349348
static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity<?> entity,
350-
Function<RelationalPersistentProperty, Object> getter) {
349+
Function<RelationalPersistentProperty, Object> getter) {
351350

352351
if (entity.hasIdProperty()) {
353352

@@ -422,7 +421,7 @@ public <T> T getPropertyValue(RelationalPersistentProperty property) {
422421
@Override
423422
public boolean hasValue(RelationalPersistentProperty property) {
424423

425-
if ((property.isCollectionLike() && property.isEntity())|| property.isMap()) {
424+
if ((property.isCollectionLike() && property.isEntity()) || property.isMap()) {
426425
// attempt relation fetch
427426
return true;
428427
}
@@ -445,12 +444,38 @@ public boolean hasValue(RelationalPersistentProperty property) {
445444
return delegate.hasValue(aggregatePath);
446445
}
447446

447+
@Override
448+
public boolean hasNonEmptyValue(RelationalPersistentProperty property) {
449+
450+
if ((property.isCollectionLike() && property.isEntity()) || property.isMap()) {
451+
// attempt relation fetch
452+
return true;
453+
}
454+
455+
AggregatePath aggregatePath = context.aggregatePath();
456+
457+
if (property.isEntity()) {
458+
459+
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property);
460+
if (entity.hasIdProperty()) {
461+
462+
RelationalPersistentProperty referenceId = entity.getRequiredIdProperty();
463+
AggregatePath toUse = aggregatePath.append(referenceId);
464+
return delegate.hasValue(toUse);
465+
}
466+
467+
return delegate.hasValue(aggregatePath.getTableInfo().reverseColumnInfo().alias());
468+
}
469+
470+
return delegate.hasNonEmptyValue(aggregatePath);
471+
}
472+
448473
@Override
449474
public RelationalPropertyValueProvider withContext(ConversionContext context) {
450475

451476
return context == this.context ? this
452477
: new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor,
453-
(ResolvingConversionContext) context, identifier);
478+
(ResolvingConversionContext) context, identifier);
454479
}
455480
}
456481

@@ -462,7 +487,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) {
462487
* @param identifier
463488
*/
464489
private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath,
465-
Identifier identifier) implements ConversionContext {
490+
Identifier identifier) implements ConversionContext {
466491

467492
@Override
468493
public <S> S convert(Object source, TypeInformation<? extends S> typeHint) {

Diff for: ‎spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java

+37
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,26 @@ void saveAndLoadAnEntityWithArray() {
681681
assertThat(reloaded.digits).isEqualTo(new String[] { "one", "two", "three" });
682682
}
683683

684+
@Test // GH-1826
685+
@EnabledOnFeature(SUPPORTS_ARRAYS)
686+
void saveAndLoadAnEntityWithEmptyArray() {
687+
688+
ArrayOwner arrayOwner = new ArrayOwner();
689+
arrayOwner.digits = new String[] { };
690+
691+
ArrayOwner saved = template.save(arrayOwner);
692+
693+
assertThat(saved.id).isNotNull();
694+
695+
ArrayOwner reloaded = template.findById(saved.id, ArrayOwner.class);
696+
697+
assertThat(reloaded).isNotNull();
698+
assertThat(reloaded.id).isEqualTo(saved.id);
699+
assertThat(reloaded.digits) //
700+
.isNotNull() //
701+
.isEmpty();
702+
}
703+
684704
@Test // DATAJDBC-259, DATAJDBC-512
685705
@EnabledOnFeature(SUPPORTS_MULTIDIMENSIONAL_ARRAYS)
686706
void saveAndLoadAnEntityWithMultidimensionalArray() {
@@ -718,6 +738,23 @@ void saveAndLoadAnEntityWithList() {
718738
assertThat(reloaded.digits).isEqualTo(asList("one", "two", "three"));
719739
}
720740

741+
@Test // DATAJDBC-1826
742+
@EnabledOnFeature(SUPPORTS_ARRAYS)
743+
void saveAndLoadAnEntityWithEmptyList() {
744+
745+
ListOwner arrayOwner = new ListOwner();
746+
747+
ListOwner saved = template.save(arrayOwner);
748+
749+
assertThat(saved.id).isNotNull();
750+
751+
ListOwner reloaded = template.findById(saved.id, ListOwner.class);
752+
753+
assertThat(reloaded).isNotNull();
754+
assertThat(reloaded.id).isEqualTo(saved.id);
755+
assertThat(reloaded.digits).isNotNull().isEmpty();
756+
}
757+
721758
@Test // GH-1033
722759
@EnabledOnFeature(SUPPORTS_ARRAYS)
723760
void saveAndLoadAnEntityWithListOfDouble() {

Diff for: ‎spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,11 @@ public boolean hasValue(RelationalPersistentProperty property) {
490490
return withContext(context.forProperty(property)).hasValue(property);
491491
}
492492

493+
@Override
494+
public boolean hasNonEmptyValue(RelationalPersistentProperty property) {
495+
return withContext(context.forProperty(property)).hasNonEmptyValue(property);
496+
}
497+
493498
@SuppressWarnings("unchecked")
494499
@Nullable
495500
@Override
@@ -565,6 +570,7 @@ private void readProperties(ConversionContext context, RelationalPersistentEntit
565570
continue;
566571
}
567572

573+
// this hasValue should actually check against null
568574
if (!valueProviderToUse.hasValue(property)) {
569575
continue;
570576
}
@@ -608,7 +614,7 @@ private boolean shouldReadEmbeddable(ConversionContext context, RelationalPersis
608614
return true;
609615
}
610616

611-
} else if (contextual.hasValue(persistentProperty)) {
617+
} else if (contextual.hasNonEmptyValue(persistentProperty)) {
612618
return true;
613619
}
614620
}
@@ -1051,6 +1057,13 @@ protected interface RelationalPropertyValueProvider extends PropertyValueProvide
10511057
*/
10521058
boolean hasValue(RelationalPersistentProperty property);
10531059

1060+
/**
1061+
* Determine whether there is a non empty value for the given {@link RelationalPersistentProperty}.
1062+
*
1063+
* @param property the property to check for whether a value is present.
1064+
*/
1065+
boolean hasNonEmptyValue(RelationalPersistentProperty property);
1066+
10541067
/**
10551068
* Contextualize this property value provider.
10561069
*
@@ -1072,6 +1085,8 @@ protected interface AggregatePathValueProvider extends RelationalPropertyValuePr
10721085
*/
10731086
boolean hasValue(AggregatePath path);
10741087

1088+
boolean hasNonEmptyValue(AggregatePath aggregatePath);
1089+
10751090
/**
10761091
* Determine whether there is a value for the given {@link SqlIdentifier}.
10771092
*
@@ -1154,6 +1169,11 @@ public boolean hasValue(RelationalPersistentProperty property) {
11541169
return accessor.hasValue(property);
11551170
}
11561171

1172+
@Override
1173+
public boolean hasNonEmptyValue(RelationalPersistentProperty property) {
1174+
return hasValue(property);
1175+
}
1176+
11571177
@Nullable
11581178
@Override
11591179
public Object getValue(AggregatePath path) {
@@ -1169,6 +1189,7 @@ public Object getValue(AggregatePath path) {
11691189

11701190
@Override
11711191
public boolean hasValue(AggregatePath path) {
1192+
11721193
Object value = document.get(path.getColumnInfo().alias().getReference());
11731194

11741195
if (value == null) {
@@ -1179,6 +1200,18 @@ public boolean hasValue(AggregatePath path) {
11791200
return true;
11801201
}
11811202

1203+
return true;
1204+
}
1205+
1206+
@Override
1207+
public boolean hasNonEmptyValue(AggregatePath path) {
1208+
1209+
if (!hasValue(path)) {
1210+
return false;
1211+
}
1212+
1213+
Object value = document.get(path.getColumnInfo().alias().getReference());
1214+
11821215
if (value instanceof Collection<?> || value.getClass().isArray()) {
11831216
return !ObjectUtils.isEmpty(value);
11841217
}

0 commit comments

Comments
 (0)
Please sign in to comment.