@@ -316,7 +316,7 @@ private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, Obj
316316 @ Override
317317 public <T > T save (T instance ) {
318318
319- return saveImpl (instance , Collections .emptyList ());
319+ return saveImpl (instance , Collections .emptyList (), null );
320320 }
321321
322322 @ Override
@@ -336,7 +336,7 @@ public <T, R> R saveAs(T instance, Class<R> resultType) {
336336 Collection <PropertyPath > pps = PropertyFilterSupport .addPropertiesFrom (instance .getClass (), resultType ,
337337 projectionFactory , neo4jMappingContext );
338338
339- T savedInstance = saveImpl (instance , pps );
339+ T savedInstance = saveImpl (instance , pps , null );
340340 if (projectionInformation .isClosed ()) {
341341 return projectionFactory .createProjection (resultType , savedInstance );
342342 }
@@ -348,7 +348,11 @@ public <T, R> R saveAs(T instance, Class<R> resultType) {
348348 this .findById (propertyAccessor .getProperty (idProperty ), savedInstance .getClass ()).get ());
349349 }
350350
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+ }
352356
353357 Neo4jPersistentEntity <?> entityMetaData = neo4jMappingContext .getPersistentEntity (instance .getClass ());
354358 boolean isEntityNew = entityMetaData .isNew (instance );
@@ -391,9 +395,17 @@ private <T> T saveImpl(T instance, Collection<PropertyPath> includedProperties)
391395 propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), internalId );
392396 }
393397 TemplateSupport .updateVersionPropertyIfPossible (entityMetaData , propertyAccessor , newOrUpdatedNode .get ());
394- processRelations (entityMetaData , instance , internalId , propertyAccessor , isEntityNew , includeProperty );
395398
396- return propertyAccessor .getBean ();
399+ if (stateMachine == null ) {
400+ stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext , instance , internalId );
401+ }
402+
403+ stateMachine .markValueAsProcessed (instance , internalId );
404+ processRelations (entityMetaData , propertyAccessor , isEntityNew , stateMachine , includeProperty );
405+
406+ T bean = propertyAccessor .getBean ();
407+ stateMachine .markValueAsProcessedAs (instance , bean );
408+ return bean ;
397409 }
398410
399411 private <T > DynamicLabels determineDynamicLabels (T entityToBeSaved , Neo4jPersistentEntity <?> entityMetaData ) {
@@ -444,7 +456,8 @@ private <T> List<T> saveAllImpl(Iterable<T> instances, List<PropertyPath> includ
444456 || entityMetaData .getDynamicLabelsProperty ().isPresent ()) {
445457 log .debug ("Saving entities using single statements." );
446458
447- return entities .stream ().map (e -> saveImpl (e , includedProperties )).collect (Collectors .toList ());
459+ NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext );
460+ return entities .stream ().map (e -> saveImpl (e , includedProperties , stateMachine )).collect (Collectors .toList ());
448461 }
449462
450463 class Tuple3 <T > {
@@ -482,7 +495,7 @@ class Tuple3<T> {
482495 Neo4jPersistentProperty idProperty = entityMetaData .getRequiredIdProperty ();
483496 Object id = convertIdValues (idProperty , propertyAccessor .getProperty (idProperty ));
484497 Long internalId = idToInternalIdMapping .get (id );
485- return processRelations (entityMetaData , t . originalInstance , internalId , propertyAccessor , t .wasNew , TemplateSupport .computeIncludePropertyPredicate (includedProperties , entityMetaData ));
498+ return this .< T > processRelations (entityMetaData , propertyAccessor , t . wasNew , new NestedRelationshipProcessingStateMachine ( neo4jMappingContext , t .originalInstance , internalId ) , TemplateSupport .computeIncludePropertyPredicate (includedProperties , entityMetaData ));
486499 }).collect (Collectors .toList ());
487500 }
488501
@@ -628,24 +641,34 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nulla
628641 * Starts of processing of the relationships.
629642 *
630643 * @param neo4jPersistentEntity The description of the instance to save
631- * @param originalInstance The original parent instance. It is paramount to pass in the original instance (prior
632- * to generating the id and prior to eventually create new instances via the property accessor,
633- * so that we can reliable stop traversing relationships.
634644 * @param parentPropertyAccessor The property accessor of the parent, to modify the relationships
635645 * @param isParentObjectNew A flag if the parent was new
646+ * @param stateMachine Initial state of entity processing
636647 * @param includeProperty A predicate telling to include a relationship property or not
648+ * @param <T> The type of the object being initially processed
649+ * @return The owner of the relations being processed
637650 */
638- private <T > T processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , T originalInstance , Long internalId ,
639- PersistentPropertyAccessor <?> parentPropertyAccessor ,
640- boolean isParentObjectNew , PropertyFilter includeProperty ) {
651+ private <T > T processRelations (
652+ Neo4jPersistentEntity <?> neo4jPersistentEntity ,
653+ PersistentPropertyAccessor <?> parentPropertyAccessor ,
654+ boolean isParentObjectNew ,
655+ NestedRelationshipProcessingStateMachine stateMachine ,
656+ PropertyFilter includeProperty
657+ ) {
641658
642659 PropertyFilter .RelaxedPropertyPath startingPropertyPath = PropertyFilter .RelaxedPropertyPath .withRootType (neo4jPersistentEntity .getUnderlyingClass ());
643660 return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew ,
644- new NestedRelationshipProcessingStateMachine ( originalInstance , internalId ) , includeProperty , startingPropertyPath );
661+ stateMachine , includeProperty , startingPropertyPath );
645662 }
646663
647- private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , PersistentPropertyAccessor <?> propertyAccessor ,
648- boolean isParentObjectNew , NestedRelationshipProcessingStateMachine stateMachine , PropertyFilter includeProperty , PropertyFilter .RelaxedPropertyPath previousPath ) {
664+ private <T > T processNestedRelations (
665+ Neo4jPersistentEntity <?> sourceEntity ,
666+ PersistentPropertyAccessor <?> propertyAccessor ,
667+ boolean isParentObjectNew ,
668+ NestedRelationshipProcessingStateMachine stateMachine ,
669+ PropertyFilter includeProperty ,
670+ PropertyFilter .RelaxedPropertyPath previousPath
671+ ) {
649672
650673 Object fromId = propertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
651674
@@ -895,12 +918,13 @@ <T, R> List<R> doSave(Iterable<R> instances, Class<T> domainType) {
895918 Collection <PropertyPath > pps = PropertyFilterSupport .addPropertiesFrom (domainType , resultType ,
896919 projectionFactory , neo4jMappingContext );
897920
921+ NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext );
898922 List <R > results = new ArrayList <>();
899923 for (R instance : instances ) {
900924 EntityFromDtoInstantiatingConverter <T > converter = new EntityFromDtoInstantiatingConverter <>(domainType , neo4jMappingContext );
901925 T domainObject = converter .convert (instance );
902926
903- T savedEntity = saveImpl (domainObject , pps );
927+ T savedEntity = saveImpl (domainObject , pps , stateMachine );
904928
905929 R convertedBack = (R ) new DtoInstantiatingConverter (resultType , neo4jMappingContext ).convertDirectly (savedEntity );
906930 results .add (convertedBack );
0 commit comments