Skip to content

Commit 00b2ab8

Browse files
committed
GH-2858 - Add re-population of properties and relationships during mapping.
Prior to this, an incomplete loaded entity, due to projection, was never touched again to add missing properties loaded via a different relationship and projection definition.
1 parent 8ff465e commit 00b2ab8

File tree

1 file changed

+75
-22
lines changed

1 file changed

+75
-22
lines changed

src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java

+75-22
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
334334

335335
// save final state of the bean
336336
knownObjects.storeObject(internalId, bean);
337+
knownObjects.mappedWithQueryResult(internalId, queryResult);
337338
return bean;
338339
};
339340

@@ -342,7 +343,8 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
342343
if (mappedObject == null) {
343344
mappedObject = mappedObjectSupplier.get();
344345
knownObjects.storeObject(internalId, mappedObject);
345-
} else if (knownObjects.alreadyMappedInPreviousRecord(internalId)) {
346+
knownObjects.mappedWithQueryResult(internalId, queryResult);
347+
} else if (knownObjects.alreadyMappedInPreviousRecord(internalId) || hasMoreFields(queryResult.asMap(), knownObjects.getQueryResultsFor(internalId))) {
346348
// If the object were created in a run before, it _could_ have missing relationships
347349
// (e.g. due to incomplete fetching by a custom query)
348350
// in such cases we will add the additional data from the next record.
@@ -357,6 +359,20 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
357359
return getMostCurrentInstance(internalId, mappedObject);
358360
}
359361

362+
private boolean hasMoreFields(Map<String, Object> currentQueryResult, Set<Map<String, Object>> savedQueryResults) {
363+
if (savedQueryResults.isEmpty()) {
364+
return true;
365+
}
366+
Set<String> currentFields = new HashSet<>(currentQueryResult.keySet());
367+
Set<String> alreadyProcessedFields = new HashSet<>();
368+
369+
for (Map<String, Object> savedQueryResult : savedQueryResults) {
370+
alreadyProcessedFields.addAll(savedQueryResult.keySet());
371+
}
372+
currentFields.removeAll(alreadyProcessedFields);
373+
return !currentFields.isEmpty();
374+
}
375+
360376
@Nullable
361377
private <ET> ET getMostCurrentInstance(String internalId, ET fallbackInstance) {
362378
return (ET) (knownObjects.getObject(internalId) != null ? knownObjects.getObject(internalId) : fallbackInstance);
@@ -383,21 +399,19 @@ private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEnt
383399
Predicate<Neo4jPersistentProperty> isConstructorParameter = concreteNodeDescription
384400
.getInstanceCreatorMetadata()::isCreatorParameter;
385401

386-
// if the object were mapped before, we assume that at least all properties are populated
387-
if (!objectAlreadyMapped) {
388-
boolean isKotlinType = KotlinDetector.isKotlinType(concreteNodeDescription.getType());
389-
// Fill simple properties
390-
PropertyHandler<Neo4jPersistentProperty> handler = populateFrom(queryResult, propertyAccessor,
391-
isConstructorParameter, nodeDescriptionAndLabels.getDynamicLabels(), lastMappedEntity, isKotlinType);
392-
PropertyHandlerSupport.of(concreteNodeDescription).doWithProperties(handler);
393-
}
402+
boolean isKotlinType = KotlinDetector.isKotlinType(concreteNodeDescription.getType());
403+
// Fill simple properties
404+
PropertyHandler<Neo4jPersistentProperty> handler = populateFrom(queryResult, propertyAccessor,
405+
isConstructorParameter, nodeDescriptionAndLabels.getDynamicLabels(), lastMappedEntity, isKotlinType, objectAlreadyMapped);
406+
PropertyHandlerSupport.of(concreteNodeDescription).doWithProperties(handler);
394407
// in a cyclic graph / with bidirectional relationships, we could end up in a state in which we
395408
// reference the start again. Because it is getting still constructed, it won't be in the knownObjects
396409
// store unless we temporarily put it there.
397410
knownObjects.storeObject(internalId, propertyAccessor.getBean());
411+
knownObjects.mappedWithQueryResult(internalId, queryResult);
398412

399413
AssociationHandlerSupport.of(concreteNodeDescription).doWithAssociations(
400-
populateFrom(queryResult, baseNodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult));
414+
populateFrom(queryResult, baseNodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult, internalId));
401415
}
402416

403417
@NonNull
@@ -499,22 +513,25 @@ public <T> T getParameterValue(Parameter<T, Neo4jPersistentProperty> parameter)
499513

500514
private PropertyHandler<Neo4jPersistentProperty> populateFrom(MapAccessor queryResult,
501515
PersistentPropertyAccessor<?> propertyAccessor, Predicate<Neo4jPersistentProperty> isConstructorParameter,
502-
Collection<String> surplusLabels, @Nullable Object targetNode, boolean ownerIsKotlinType) {
516+
Collection<String> surplusLabels, @Nullable Object targetNode, boolean ownerIsKotlinType, boolean objectAlreadyMapped) {
503517

504518
return property -> {
505519
if (isConstructorParameter.test(property)) {
506520
return;
507521
}
508522

509523
TypeInformation<?> typeInformation = property.getTypeInformation();
510-
if (property.isDynamicLabels()) {
511-
propertyAccessor.setProperty(property,
512-
createDynamicLabelsProperty(typeInformation, surplusLabels));
513-
} else if (property.isAnnotationPresent(TargetNode.class)) {
514-
if (queryResult instanceof Relationship) {
515-
propertyAccessor.setProperty(property, targetNode);
524+
if (!objectAlreadyMapped) {
525+
if (property.isDynamicLabels()) {
526+
propertyAccessor.setProperty(property,
527+
createDynamicLabelsProperty(typeInformation, surplusLabels));
528+
} else if (property.isAnnotationPresent(TargetNode.class)) {
529+
if (queryResult instanceof Relationship) {
530+
propertyAccessor.setProperty(property, targetNode);
531+
}
516532
}
517-
} else {
533+
}
534+
if (!property.isDynamicLabels() && !property.isAnnotationPresent(TargetNode.class)) {
518535
Object value = conversionService.readValue(extractValueOf(property, queryResult), typeInformation, property.getOptionalConverter());
519536
if (value != null) {
520537
Class<?> rawType = typeInformation.getType();
@@ -532,7 +549,7 @@ private static Object getValueOrDefault(boolean ownerIsKotlinType, Class<?> rawT
532549

533550
private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor queryResult, NodeDescription<?> baseDescription,
534551
PersistentPropertyAccessor<?> propertyAccessor, Predicate<Neo4jPersistentProperty> isConstructorParameter,
535-
boolean objectAlreadyMapped, Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult) {
552+
boolean objectAlreadyMapped, Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult, String internalId) {
536553

537554
return association -> {
538555

@@ -555,15 +572,15 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
555572

556573
boolean propertyValueNotNull = propertyValue != null;
557574

558-
boolean populatedCollection = persistentProperty.isCollectionLike()
575+
boolean populatedCollection = objectAlreadyMapped && persistentProperty.isCollectionLike()
559576
&& propertyValueNotNull
560577
&& !((Collection<?>) propertyValue).isEmpty();
561578

562-
boolean populatedMap = persistentProperty.isMap()
579+
boolean populatedMap = objectAlreadyMapped && persistentProperty.isMap()
563580
&& propertyValueNotNull
564581
&& !((Map<?, ?>) propertyValue).isEmpty();
565582

566-
boolean populatedScalarValue = !persistentProperty.isCollectionLike() && !persistentProperty.isMap()
583+
boolean populatedScalarValue = objectAlreadyMapped && !persistentProperty.isCollectionLike() && !persistentProperty.isMap()
567584
&& propertyValueNotNull;
568585

569586
if (populatedCollection) {
@@ -592,6 +609,7 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
592609

593610
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult)
594611
.ifPresent(value -> propertyAccessor.setProperty(persistentProperty, value));
612+
595613
};
596614
}
597615

@@ -903,6 +921,7 @@ static class KnownObjects {
903921
private final Set<String> idsInCreation = new HashSet<>();
904922

905923
private final Map<String, Integer> processedRelationships = new HashMap<>();
924+
private final Map<Map<String, Object>, String> dings = new HashMap<>();
906925

907926
private void storeObject(@Nullable String internalId, Object object) {
908927
if (internalId == null) {
@@ -1029,5 +1048,39 @@ private void nextRecord() {
10291048
previousRecords.addAll(internalCurrentRecord.keySet());
10301049
internalCurrentRecord.clear();
10311050
}
1051+
1052+
public void mappedWithQueryResult(String internalId, MapAccessor queryResult) {
1053+
try {
1054+
write.lock();
1055+
dings.put(queryResult.asMap(), internalId);
1056+
} finally {
1057+
write.unlock();
1058+
}
1059+
}
1060+
1061+
public boolean wasMappedWithQueryResult(String internalId, MapAccessor queryResult) {
1062+
try {
1063+
read.lock();
1064+
String potentialId = dings.get(queryResult.asMap());
1065+
return potentialId != null && potentialId.equals(internalId);
1066+
} finally {
1067+
read.unlock();
1068+
}
1069+
}
1070+
1071+
public Set<Map<String, Object>> getQueryResultsFor(String internalId) {
1072+
try {
1073+
Set<Map<String, Object>> matchingQueryResults = new HashSet<>();
1074+
read.lock();
1075+
for (Map.Entry<Map<String, Object>, String> queryResultsForInternalId : dings.entrySet()) {
1076+
if (queryResultsForInternalId.getValue().equals(internalId)) {
1077+
matchingQueryResults.add(queryResultsForInternalId.getKey());
1078+
}
1079+
}
1080+
return matchingQueryResults;
1081+
} finally {
1082+
read.unlock();
1083+
}
1084+
}
10321085
}
10331086
}

0 commit comments

Comments
 (0)