@@ -316,7 +316,7 @@ private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, Obj
316
316
@ Override
317
317
public <T > T save (T instance ) {
318
318
319
- return saveImpl (instance , Collections .emptyList ());
319
+ return saveImpl (instance , Collections .emptyList (), null );
320
320
}
321
321
322
322
@ Override
@@ -336,7 +336,7 @@ public <T, R> R saveAs(T instance, Class<R> resultType) {
336
336
Collection <PropertyPath > pps = PropertyFilterSupport .addPropertiesFrom (instance .getClass (), resultType ,
337
337
projectionFactory , neo4jMappingContext );
338
338
339
- T savedInstance = saveImpl (instance , pps );
339
+ T savedInstance = saveImpl (instance , pps , null );
340
340
if (projectionInformation .isClosed ()) {
341
341
return projectionFactory .createProjection (resultType , savedInstance );
342
342
}
@@ -348,7 +348,11 @@ public <T, R> R saveAs(T instance, Class<R> resultType) {
348
348
this .findById (propertyAccessor .getProperty (idProperty ), savedInstance .getClass ()).get ());
349
349
}
350
350
351
- private <T > T saveImpl (T instance , Collection <PropertyPath > includedProperties ) {
351
+ private <T > T saveImpl (T instance , @ Nullable Collection <PropertyPath > includedProperties , @ Nullable NestedRelationshipProcessingStateMachine stateMachine ) {
352
+
353
+ if (stateMachine != null && stateMachine .hasProcessedValue (instance )) {
354
+ return instance ;
355
+ }
352
356
353
357
Neo4jPersistentEntity <?> entityMetaData = neo4jMappingContext .getRequiredPersistentEntity (instance .getClass ());
354
358
boolean isEntityNew = entityMetaData .isNew (instance );
@@ -393,9 +397,17 @@ private <T> T saveImpl(T instance, Collection<PropertyPath> includedProperties)
393
397
propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), internalId );
394
398
}
395
399
TemplateSupport .updateVersionPropertyIfPossible (entityMetaData , propertyAccessor , newOrUpdatedNode .get ());
396
- processRelations (entityMetaData , instance , internalId , propertyAccessor , isEntityNew , includeProperty );
397
400
398
- return propertyAccessor .getBean ();
401
+ if (stateMachine == null ) {
402
+ stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext , instance , internalId );
403
+ }
404
+
405
+ stateMachine .markValueAsProcessed (instance , internalId );
406
+ processRelations (entityMetaData , propertyAccessor , isEntityNew , stateMachine , includeProperty );
407
+
408
+ T bean = propertyAccessor .getBean ();
409
+ stateMachine .markValueAsProcessedAs (instance , bean );
410
+ return bean ;
399
411
}
400
412
401
413
@ SuppressWarnings ("unchecked" )
@@ -447,7 +459,8 @@ private <T> List<T> saveAllImpl(Iterable<T> instances, List<PropertyPath> includ
447
459
|| entityMetaData .getDynamicLabelsProperty ().isPresent ()) {
448
460
log .debug ("Saving entities using single statements." );
449
461
450
- return entities .stream ().map (e -> saveImpl (e , includedProperties )).collect (Collectors .toList ());
462
+ NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext );
463
+ return entities .stream ().map (e -> saveImpl (e , includedProperties , stateMachine )).collect (Collectors .toList ());
451
464
}
452
465
453
466
class Tuple3 <T > {
@@ -486,7 +499,7 @@ class Tuple3<T> {
486
499
Neo4jPersistentProperty idProperty = entityMetaData .getRequiredIdProperty ();
487
500
Object id = convertIdValues (idProperty , propertyAccessor .getProperty (idProperty ));
488
501
Long internalId = idToInternalIdMapping .get (id );
489
- return processRelations (entityMetaData , t . originalInstance , internalId , propertyAccessor , t .wasNew , TemplateSupport .computeIncludePropertyPredicate (includedProperties , entityMetaData ));
502
+ return this .< T > processRelations (entityMetaData , propertyAccessor , t . wasNew , new NestedRelationshipProcessingStateMachine ( neo4jMappingContext , t .originalInstance , internalId ) , TemplateSupport .computeIncludePropertyPredicate (includedProperties , entityMetaData ));
490
503
}).collect (Collectors .toList ());
491
504
}
492
505
@@ -634,24 +647,34 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nulla
634
647
* Starts of processing of the relationships.
635
648
*
636
649
* @param neo4jPersistentEntity The description of the instance to save
637
- * @param originalInstance The original parent instance. It is paramount to pass in the original instance (prior
638
- * to generating the id and prior to eventually create new instances via the property accessor,
639
- * so that we can reliable stop traversing relationships.
640
650
* @param parentPropertyAccessor The property accessor of the parent, to modify the relationships
641
651
* @param isParentObjectNew A flag if the parent was new
652
+ * @param stateMachine Initial state of entity processing
642
653
* @param includeProperty A predicate telling to include a relationship property or not
654
+ * @param <T> The type of the object being initially processed
655
+ * @return The owner of the relations being processed
643
656
*/
644
- private <T > T processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , T originalInstance , Long internalId ,
645
- PersistentPropertyAccessor <?> parentPropertyAccessor ,
646
- boolean isParentObjectNew , PropertyFilter includeProperty ) {
657
+ private <T > T processRelations (
658
+ Neo4jPersistentEntity <?> neo4jPersistentEntity ,
659
+ PersistentPropertyAccessor <?> parentPropertyAccessor ,
660
+ boolean isParentObjectNew ,
661
+ NestedRelationshipProcessingStateMachine stateMachine ,
662
+ PropertyFilter includeProperty
663
+ ) {
647
664
648
665
PropertyFilter .RelaxedPropertyPath startingPropertyPath = PropertyFilter .RelaxedPropertyPath .withRootType (neo4jPersistentEntity .getUnderlyingClass ());
649
666
return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew ,
650
- new NestedRelationshipProcessingStateMachine ( originalInstance , internalId ) , includeProperty , startingPropertyPath );
667
+ stateMachine , includeProperty , startingPropertyPath );
651
668
}
652
669
653
- private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , PersistentPropertyAccessor <?> propertyAccessor ,
654
- boolean isParentObjectNew , NestedRelationshipProcessingStateMachine stateMachine , PropertyFilter includeProperty , PropertyFilter .RelaxedPropertyPath previousPath ) {
670
+ private <T > T processNestedRelations (
671
+ Neo4jPersistentEntity <?> sourceEntity ,
672
+ PersistentPropertyAccessor <?> propertyAccessor ,
673
+ boolean isParentObjectNew ,
674
+ NestedRelationshipProcessingStateMachine stateMachine ,
675
+ PropertyFilter includeProperty ,
676
+ PropertyFilter .RelaxedPropertyPath previousPath
677
+ ) {
655
678
656
679
Object fromId = propertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
657
680
@@ -908,12 +931,13 @@ <T, R> List<R> doSave(Iterable<R> instances, Class<T> domainType) {
908
931
Collection <PropertyPath > pps = PropertyFilterSupport .addPropertiesFrom (domainType , resultType ,
909
932
projectionFactory , neo4jMappingContext );
910
933
934
+ NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext );
911
935
List <R > results = new ArrayList <>();
912
936
for (R instance : instances ) {
913
937
EntityFromDtoInstantiatingConverter <T > converter = new EntityFromDtoInstantiatingConverter <>(domainType , neo4jMappingContext );
914
938
T domainObject = converter .convert (instance );
915
939
916
- T savedEntity = saveImpl (domainObject , pps );
940
+ T savedEntity = saveImpl (domainObject , pps , stateMachine );
917
941
918
942
@ SuppressWarnings ("unchecked" )
919
943
R convertedBack = (R ) new DtoInstantiatingConverter (resultType , neo4jMappingContext ).convertDirectly (savedEntity );
0 commit comments