@@ -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 .getPersistentEntity (instance .getClass ());
354
358
boolean isEntityNew = entityMetaData .isNew (instance );
@@ -391,9 +395,17 @@ private <T> T saveImpl(T instance, Collection<PropertyPath> includedProperties)
391
395
propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), internalId );
392
396
}
393
397
TemplateSupport .updateVersionPropertyIfPossible (entityMetaData , propertyAccessor , newOrUpdatedNode .get ());
394
- processRelations (entityMetaData , instance , internalId , propertyAccessor , isEntityNew , includeProperty );
395
398
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 ;
397
409
}
398
410
399
411
private <T > DynamicLabels determineDynamicLabels (T entityToBeSaved , Neo4jPersistentEntity <?> entityMetaData ) {
@@ -444,7 +456,8 @@ private <T> List<T> saveAllImpl(Iterable<T> instances, List<PropertyPath> includ
444
456
|| entityMetaData .getDynamicLabelsProperty ().isPresent ()) {
445
457
log .debug ("Saving entities using single statements." );
446
458
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 ());
448
461
}
449
462
450
463
class Tuple3 <T > {
@@ -482,7 +495,7 @@ class Tuple3<T> {
482
495
Neo4jPersistentProperty idProperty = entityMetaData .getRequiredIdProperty ();
483
496
Object id = convertIdValues (idProperty , propertyAccessor .getProperty (idProperty ));
484
497
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 ));
486
499
}).collect (Collectors .toList ());
487
500
}
488
501
@@ -628,24 +641,34 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nulla
628
641
* Starts of processing of the relationships.
629
642
*
630
643
* @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.
634
644
* @param parentPropertyAccessor The property accessor of the parent, to modify the relationships
635
645
* @param isParentObjectNew A flag if the parent was new
646
+ * @param stateMachine Initial state of entity processing
636
647
* @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
637
650
*/
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
+ ) {
641
658
642
659
PropertyFilter .RelaxedPropertyPath startingPropertyPath = PropertyFilter .RelaxedPropertyPath .withRootType (neo4jPersistentEntity .getUnderlyingClass ());
643
660
return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew ,
644
- new NestedRelationshipProcessingStateMachine ( originalInstance , internalId ) , includeProperty , startingPropertyPath );
661
+ stateMachine , includeProperty , startingPropertyPath );
645
662
}
646
663
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
+ ) {
649
672
650
673
Object fromId = propertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
651
674
@@ -895,12 +918,13 @@ <T, R> List<R> doSave(Iterable<R> instances, Class<T> domainType) {
895
918
Collection <PropertyPath > pps = PropertyFilterSupport .addPropertiesFrom (domainType , resultType ,
896
919
projectionFactory , neo4jMappingContext );
897
920
921
+ NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext );
898
922
List <R > results = new ArrayList <>();
899
923
for (R instance : instances ) {
900
924
EntityFromDtoInstantiatingConverter <T > converter = new EntityFromDtoInstantiatingConverter <>(domainType , neo4jMappingContext );
901
925
T domainObject = converter .convert (instance );
902
926
903
- T savedEntity = saveImpl (domainObject , pps );
927
+ T savedEntity = saveImpl (domainObject , pps , stateMachine );
904
928
905
929
R convertedBack = (R ) new DtoInstantiatingConverter (resultType , neo4jMappingContext ).convertDirectly (savedEntity );
906
930
results .add (convertedBack );
0 commit comments