@@ -334,6 +334,7 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
334
334
335
335
// save final state of the bean
336
336
knownObjects .storeObject (internalId , bean );
337
+ knownObjects .mappedWithQueryResult (internalId , queryResult );
337
338
return bean ;
338
339
};
339
340
@@ -342,7 +343,8 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
342
343
if (mappedObject == null ) {
343
344
mappedObject = mappedObjectSupplier .get ();
344
345
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 ))) {
346
348
// If the object were created in a run before, it _could_ have missing relationships
347
349
// (e.g. due to incomplete fetching by a custom query)
348
350
// 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
357
359
return getMostCurrentInstance (internalId , mappedObject );
358
360
}
359
361
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
+
360
376
@ Nullable
361
377
private <ET > ET getMostCurrentInstance (String internalId , ET fallbackInstance ) {
362
378
return (ET ) (knownObjects .getObject (internalId ) != null ? knownObjects .getObject (internalId ) : fallbackInstance );
@@ -383,21 +399,19 @@ private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEnt
383
399
Predicate <Neo4jPersistentProperty > isConstructorParameter = concreteNodeDescription
384
400
.getInstanceCreatorMetadata ()::isCreatorParameter ;
385
401
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 );
394
407
// in a cyclic graph / with bidirectional relationships, we could end up in a state in which we
395
408
// reference the start again. Because it is getting still constructed, it won't be in the knownObjects
396
409
// store unless we temporarily put it there.
397
410
knownObjects .storeObject (internalId , propertyAccessor .getBean ());
411
+ knownObjects .mappedWithQueryResult (internalId , queryResult );
398
412
399
413
AssociationHandlerSupport .of (concreteNodeDescription ).doWithAssociations (
400
- populateFrom (queryResult , baseNodeDescription , propertyAccessor , isConstructorParameter , objectAlreadyMapped , relationshipsFromResult , nodesFromResult ));
414
+ populateFrom (queryResult , baseNodeDescription , propertyAccessor , isConstructorParameter , objectAlreadyMapped , relationshipsFromResult , nodesFromResult , internalId ));
401
415
}
402
416
403
417
@ NonNull
@@ -499,22 +513,25 @@ public <T> T getParameterValue(Parameter<T, Neo4jPersistentProperty> parameter)
499
513
500
514
private PropertyHandler <Neo4jPersistentProperty > populateFrom (MapAccessor queryResult ,
501
515
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 ) {
503
517
504
518
return property -> {
505
519
if (isConstructorParameter .test (property )) {
506
520
return ;
507
521
}
508
522
509
523
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
+ }
516
532
}
517
- } else {
533
+ }
534
+ if (!property .isDynamicLabels () && !property .isAnnotationPresent (TargetNode .class )) {
518
535
Object value = conversionService .readValue (extractValueOf (property , queryResult ), typeInformation , property .getOptionalConverter ());
519
536
if (value != null ) {
520
537
Class <?> rawType = typeInformation .getType ();
@@ -532,7 +549,7 @@ private static Object getValueOrDefault(boolean ownerIsKotlinType, Class<?> rawT
532
549
533
550
private AssociationHandler <Neo4jPersistentProperty > populateFrom (MapAccessor queryResult , NodeDescription <?> baseDescription ,
534
551
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 ) {
536
553
537
554
return association -> {
538
555
@@ -555,15 +572,15 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
555
572
556
573
boolean propertyValueNotNull = propertyValue != null ;
557
574
558
- boolean populatedCollection = persistentProperty .isCollectionLike ()
575
+ boolean populatedCollection = objectAlreadyMapped && persistentProperty .isCollectionLike ()
559
576
&& propertyValueNotNull
560
577
&& !((Collection <?>) propertyValue ).isEmpty ();
561
578
562
- boolean populatedMap = persistentProperty .isMap ()
579
+ boolean populatedMap = objectAlreadyMapped && persistentProperty .isMap ()
563
580
&& propertyValueNotNull
564
581
&& !((Map <?, ?>) propertyValue ).isEmpty ();
565
582
566
- boolean populatedScalarValue = !persistentProperty .isCollectionLike () && !persistentProperty .isMap ()
583
+ boolean populatedScalarValue = objectAlreadyMapped && !persistentProperty .isCollectionLike () && !persistentProperty .isMap ()
567
584
&& propertyValueNotNull ;
568
585
569
586
if (populatedCollection ) {
@@ -592,6 +609,7 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
592
609
593
610
createInstanceOfRelationships (persistentProperty , queryResult , (RelationshipDescription ) association , baseDescription , relationshipsFromResult , nodesFromResult )
594
611
.ifPresent (value -> propertyAccessor .setProperty (persistentProperty , value ));
612
+
595
613
};
596
614
}
597
615
@@ -903,6 +921,7 @@ static class KnownObjects {
903
921
private final Set <String > idsInCreation = new HashSet <>();
904
922
905
923
private final Map <String , Integer > processedRelationships = new HashMap <>();
924
+ private final Map <Map <String , Object >, String > dings = new HashMap <>();
906
925
907
926
private void storeObject (@ Nullable String internalId , Object object ) {
908
927
if (internalId == null ) {
@@ -1029,5 +1048,39 @@ private void nextRecord() {
1029
1048
previousRecords .addAll (internalCurrentRecord .keySet ());
1030
1049
internalCurrentRecord .clear ();
1031
1050
}
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
+ }
1032
1085
}
1033
1086
}
0 commit comments