@@ -144,7 +144,7 @@ private <R> MapAccessor determineQueryRoot(MapAccessor mapAccessor, @Nullable Ne
144
144
Node node = value .asNode ();
145
145
if (primaryLabels .stream ().anyMatch (node ::hasLabel )) { // it has a matching label
146
146
// We haven't seen this node yet, so we take it
147
- if (! knownObjects .containsNode ( node )) {
147
+ if (knownObjects .getObject ( "N" + IdentitySupport . getInternalId ( node )) == null ) {
148
148
matchingNodes .add (node );
149
149
} else {
150
150
seenMatchingNodes .add (node );
@@ -264,25 +264,26 @@ private static MapAccessor mergeRootNodeWithRecord(Node node, MapAccessor record
264
264
private <ET > ET map (MapAccessor queryResult , MapAccessor allValues , Neo4jPersistentEntity <ET > nodeDescription ) {
265
265
Collection <Relationship > relationshipsFromResult = extractRelationships (allValues );
266
266
Collection <Node > nodesFromResult = extractNodes (allValues );
267
- return map (queryResult , nodeDescription , null , relationshipsFromResult , nodesFromResult );
267
+ return map (queryResult , nodeDescription , null , null , relationshipsFromResult , nodesFromResult );
268
268
}
269
269
270
270
private <ET > ET map (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription ,
271
- @ Nullable Object lastMappedEntity , Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult ) {
271
+ @ Nullable Object lastMappedEntity , @ Nullable RelationshipDescription relationshipDescription , Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult ) {
272
272
273
273
// prior to SDN 7 local `getInternalId` didn't check relationships, so in that case, they have never been a known
274
274
// object. The centralized methods checks those too now. The condition is to recreate the old behaviour without
275
275
// losing the central access. The behaviour of knowObjects should take different sources of ids into account,
276
276
// as relationships and nodes might have overlapping values
277
- Long internalId = queryResult instanceof Relationship ? null : IdentitySupport .getInternalId (queryResult );
277
+ String direction = relationshipDescription != null ? relationshipDescription .getDirection ().name () : null ;
278
+ String internalId = IdentitySupport .getInternalId (queryResult , direction );
278
279
279
280
Supplier <ET > mappedObjectSupplier = () -> {
280
281
if (knownObjects .isInCreation (internalId )) {
281
282
throw new MappingException (
282
283
String .format (
283
284
"The node with id %s has a logical cyclic mapping dependency; " +
284
285
"its creation caused the creation of another node that has a reference to this" ,
285
- internalId )
286
+ internalId . substring ( 1 ) )
286
287
);
287
288
}
288
289
knownObjects .setInCreation (internalId );
@@ -330,7 +331,7 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
330
331
}
331
332
332
333
333
- private <ET > void populateProperties (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription , Long internalId ,
334
+ private <ET > void populateProperties (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription , String internalId ,
334
335
ET mappedObject , @ Nullable Object lastMappedEntity ,
335
336
Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult , boolean objectAlreadyMapped ) {
336
337
@@ -533,6 +534,23 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
533
534
boolean populatedScalarValue = !persistentProperty .isCollectionLike () && !persistentProperty .isMap ()
534
535
&& propertyValueNotNull ;
535
536
537
+ if (populatedCollection ) {
538
+ createInstanceOfRelationships (persistentProperty , queryResult , (RelationshipDescription ) association , baseDescription , relationshipsFromResult , nodesFromResult , false )
539
+ .ifPresent (value -> {
540
+ Collection <?> providedCollection = (Collection <?>) value ;
541
+ Collection <?> existingValue = (Collection <?>) propertyValue ;
542
+ Collection <Object > newValue = CollectionFactory .createCollection (existingValue .getClass (), providedCollection .size () + existingValue .size ());
543
+
544
+ RelationshipDescription relationshipDescription = (RelationshipDescription ) association ;
545
+ Map <Object , Object > mergedValues = new HashMap <>();
546
+ mergeCollections (relationshipDescription , existingValue , mergedValues );
547
+ mergeCollections (relationshipDescription , providedCollection , mergedValues );
548
+
549
+ newValue .addAll (mergedValues .values ());
550
+ propertyAccessor .setProperty (persistentProperty , newValue );
551
+ });
552
+ }
553
+
536
554
boolean propertyAlreadyPopulated = populatedCollection || populatedMap || populatedScalarValue ;
537
555
538
556
// avoid unnecessary re-assignment of values
@@ -545,9 +563,33 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
545
563
};
546
564
}
547
565
566
+ private void mergeCollections (RelationshipDescription relationshipDescription , Collection <?> values , Map <Object , Object > mergedValues ) {
567
+ for (Object existingValueInCollection : values ) {
568
+ if (relationshipDescription .hasRelationshipProperties ()) {
569
+ Object existingIdPropertyValue = ((Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity ())
570
+ .getPropertyAccessor (existingValueInCollection )
571
+ .getProperty (((Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity ()).getIdProperty ());
572
+
573
+ mergedValues .put (existingIdPropertyValue , existingValueInCollection );
574
+ } else if (!relationshipDescription .isDynamic ()) { // should not happen because this is all inside populatedCollection (but better safe than sorry)
575
+ Object existingIdPropertyValue = ((Neo4jPersistentEntity <?>) relationshipDescription .getTarget ())
576
+ .getPropertyAccessor (existingValueInCollection )
577
+ .getProperty (((Neo4jPersistentEntity <?>) relationshipDescription .getTarget ()).getIdProperty ());
578
+
579
+ mergedValues .put (existingIdPropertyValue , existingValueInCollection );
580
+ }
581
+ }
582
+ }
583
+
584
+ private Optional <Object > createInstanceOfRelationships (Neo4jPersistentProperty persistentProperty , MapAccessor values ,
585
+ RelationshipDescription relationshipDescription , NodeDescription <?> baseDescription , Collection <Relationship > relationshipsFromResult ,
586
+ Collection <Node > nodesFromResult ) {
587
+ return createInstanceOfRelationships (persistentProperty , values , relationshipDescription , baseDescription , relationshipsFromResult , nodesFromResult , true );
588
+ }
589
+
548
590
private Optional <Object > createInstanceOfRelationships (Neo4jPersistentProperty persistentProperty , MapAccessor values ,
549
591
RelationshipDescription relationshipDescription , NodeDescription <?> baseDescription , Collection <Relationship > relationshipsFromResult ,
550
- Collection <Node > nodesFromResult ) {
592
+ Collection <Node > nodesFromResult , boolean fetchMore ) {
551
593
552
594
String typeOfRelationship = relationshipDescription .getType ();
553
595
String targetLabel = relationshipDescription .getTarget ().getPrimaryLabel ();
@@ -588,6 +630,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
588
630
589
631
if (Values .NULL .equals (list )) {
590
632
Long sourceNodeId = IdentitySupport .getInternalId (values );
633
+ //Long sourceNodeId = getInternalIdAsLong(values);
591
634
592
635
Function <Relationship , Long > sourceIdSelector = relationshipDescription .isIncoming () ? IdentitySupport ::getEndId : IdentitySupport ::getStartId ;
593
636
Function <Relationship , Long > targetIdSelector = relationshipDescription .isIncoming () ? IdentitySupport ::getStartId : IdentitySupport ::getEndId ;
@@ -614,22 +657,37 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
614
657
// If this relationship got processed twice (OUTGOING, INCOMING), it is never needed again
615
658
// and therefor should not be in the list.
616
659
// Otherwise, for highly linked data it could potentially cause a StackOverflowError.
617
- if (knownObjects .hasProcessedRelationshipCompletely (IdentitySupport .getInternalId (possibleRelationship ))) {
660
+ String direction = relationshipDescription .getDirection ().name ();
661
+ if (knownObjects .hasProcessedRelationshipCompletely ("R" + direction + IdentitySupport .getInternalId (possibleRelationship ))) {
618
662
relationshipsFromResult .remove (possibleRelationship );
619
663
}
620
664
// If the target is the same(equal) node, get the related object from the cache.
621
665
// Avoiding the call to the map method also breaks an endless cycle of trying to finish
622
666
// the property population of _this_ object.
623
667
// The initial population will happen at the end of this mapping. This is sufficient because
624
668
// it only affects properties not changing the instance of the object.
625
- Object mappedObject = sourceNodeId != null && sourceNodeId .equals (targetNodeId )
626
- ? knownObjects .getObject (sourceNodeId )
627
- : map (possibleValueNode , concreteTargetNodeDescription , null , relationshipsFromResult , nodesFromResult );
628
- if (relationshipDescription .hasRelationshipProperties ()) {
669
+ Object mappedObject ;
670
+ if (fetchMore ) {
671
+ mappedObject = sourceNodeId != null && sourceNodeId .equals (targetNodeId )
672
+ ? knownObjects .getObject ("N" + sourceNodeId )
673
+ : map (possibleValueNode , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
674
+ } else {
675
+ Object objectFromStore = knownObjects .getObject ("N" + targetNodeId );
676
+ mappedObject = objectFromStore != null
677
+ ? objectFromStore
678
+ : map (possibleValueNode , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
679
+ }
629
680
630
- Object relationshipProperties = map (possibleRelationship ,
631
- (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (),
632
- mappedObject , relationshipsFromResult , nodesFromResult );
681
+ if (relationshipDescription .hasRelationshipProperties ()) {
682
+ Object relationshipProperties ;
683
+ if (fetchMore ) {
684
+ relationshipProperties = map (possibleRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), mappedObject , relationshipDescription , relationshipsFromResult , nodesFromResult );
685
+ } else {
686
+ Object objectFromStore = knownObjects .getObject (IdentitySupport .getInternalId (possibleRelationship , relationshipDescription .getDirection ().name ()));
687
+ relationshipProperties = objectFromStore != null
688
+ ? objectFromStore
689
+ : map (possibleRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), mappedObject , relationshipDescription , relationshipsFromResult , nodesFromResult );
690
+ }
633
691
relationshipsAndProperties .add (relationshipProperties );
634
692
mappedObjectHandler .accept (possibleRelationship .type (), relationshipProperties );
635
693
} else {
@@ -646,7 +704,15 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
646
704
Neo4jPersistentEntity <?> concreteTargetNodeDescription =
647
705
getMostConcreteTargetNodeDescription (genericTargetNodeDescription , relatedEntity );
648
706
649
- Object valueEntry = map (relatedEntity , concreteTargetNodeDescription , null , relationshipsFromResult , nodesFromResult );
707
+ Object valueEntry ;
708
+ if (fetchMore ) {
709
+ valueEntry = map (relatedEntity , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
710
+ } else {
711
+ Object objectFromStore = knownObjects .getObject (IdentitySupport .getInternalId (relatedEntity , null ));
712
+ valueEntry = objectFromStore != null
713
+ ? objectFromStore
714
+ : map (relatedEntity , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
715
+ }
650
716
651
717
if (relationshipDescription .hasRelationshipProperties ()) {
652
718
String sourceLabel = relationshipDescription .getSource ().getMostAbstractParentLabel (baseDescription );
@@ -655,9 +721,16 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
655
721
Relationship relatedEntityRelationship = relatedEntity .get (relationshipSymbolicName )
656
722
.asRelationship ();
657
723
658
- Object relationshipProperties = map (relatedEntityRelationship ,
659
- (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (),
660
- valueEntry , relationshipsFromResult , nodesFromResult );
724
+ Object relationshipProperties ;
725
+ if (fetchMore ) {
726
+ relationshipProperties = map (relatedEntityRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), valueEntry , relationshipDescription , relationshipsFromResult , nodesFromResult );
727
+ } else {
728
+ Object objectFromStore = knownObjects .getObject (IdentitySupport .getInternalId (relatedEntityRelationship , relationshipDescription .getDirection ().name ()));
729
+ relationshipProperties = objectFromStore != null
730
+ ? objectFromStore
731
+ : map (relatedEntityRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), valueEntry , relationshipDescription , relationshipsFromResult , nodesFromResult );
732
+ }
733
+
661
734
relationshipsAndProperties .add (relationshipProperties );
662
735
mappedObjectHandler .accept (relatedEntity .get (RelationshipDescription .NAME_OF_RELATIONSHIP_TYPE ).asString (), relationshipProperties );
663
736
} else {
@@ -773,14 +846,14 @@ static class KnownObjects {
773
846
private final Lock read = lock .readLock ();
774
847
private final Lock write = lock .writeLock ();
775
848
776
- private final Map <Long , Object > internalIdStore = new HashMap <>();
777
- private final Map <Long , Boolean > internalCurrentRecord = new HashMap <>();
778
- private final Set <Long > previousRecords = new HashSet <>();
779
- private final Set <Long > idsInCreation = new HashSet <>();
849
+ private final Map <String , Object > internalIdStore = new HashMap <>();
850
+ private final Map <String , Boolean > internalCurrentRecord = new HashMap <>();
851
+ private final Set <String > previousRecords = new HashSet <>();
852
+ private final Set <String > idsInCreation = new HashSet <>();
780
853
781
- private final Map <Long , Integer > processedRelationships = new HashMap <>();
854
+ private final Map <String , Integer > processedRelationships = new HashMap <>();
782
855
783
- private void storeObject (@ Nullable Long internalId , Object object ) {
856
+ private void storeObject (@ Nullable String internalId , Object object ) {
784
857
if (internalId == null ) {
785
858
return ;
786
859
}
@@ -794,7 +867,7 @@ private void storeObject(@Nullable Long internalId, Object object) {
794
867
}
795
868
}
796
869
797
- private void setInCreation (@ Nullable Long internalId ) {
870
+ private void setInCreation (@ Nullable String internalId ) {
798
871
if (internalId == null ) {
799
872
return ;
800
873
}
@@ -806,7 +879,7 @@ private void setInCreation(@Nullable Long internalId) {
806
879
}
807
880
}
808
881
809
- private boolean isInCreation (@ Nullable Long internalId ) {
882
+ private boolean isInCreation (@ Nullable String internalId ) {
810
883
if (internalId == null ) {
811
884
return false ;
812
885
}
@@ -829,8 +902,7 @@ private boolean containsNode(Node node) {
829
902
}
830
903
831
904
@ Nullable
832
- private Object getObject (@ Nullable Long internalId ) {
833
-
905
+ private Object getObject (@ Nullable String internalId ) {
834
906
if (internalId == null ) {
835
907
return null ;
836
908
}
@@ -842,7 +914,7 @@ private Object getObject(@Nullable Long internalId) {
842
914
}
843
915
}
844
916
845
- private void removeFromInCreation (@ Nullable Long internalId ) {
917
+ private void removeFromInCreation (@ Nullable String internalId ) {
846
918
if (internalId == null ) {
847
919
return ;
848
920
}
@@ -854,7 +926,7 @@ private void removeFromInCreation(@Nullable Long internalId) {
854
926
}
855
927
}
856
928
857
- private boolean alreadyMappedInPreviousRecord (@ Nullable Long internalId ) {
929
+ private boolean alreadyMappedInPreviousRecord (@ Nullable String internalId ) {
858
930
if (internalId == null ) {
859
931
return false ;
860
932
}
@@ -874,7 +946,7 @@ private boolean alreadyMappedInPreviousRecord(@Nullable Long internalId) {
874
946
* It increases the process count of relationships (mapped by their ids)
875
947
* AND checks if it was already processed twice (INCOMING/OUTGOING).
876
948
*/
877
- private boolean hasProcessedRelationshipCompletely (Long relationshipId ) {
949
+ private boolean hasProcessedRelationshipCompletely (String relationshipId ) {
878
950
try {
879
951
write .lock ();
880
952
0 commit comments