@@ -251,9 +251,10 @@ private <T> T saveImpl(T instance, @Nullable String inDatabase) {
251251 PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (entityToBeSaved );
252252 if (entityMetaData .isUsingInternalIds ()) {
253253 propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), optionalInternalId .get ());
254- entityToBeSaved = propertyAccessor .getBean ();
255254 }
256- return processRelations (entityMetaData , entityToBeSaved , isEntityNew , inDatabase , instance );
255+ processRelations (entityMetaData , instance , propertyAccessor , inDatabase , isEntityNew );
256+
257+ return propertyAccessor .getBean ();
257258 }
258259
259260 private <T > DynamicLabels determineDynamicLabels (T entityToBeSaved , Neo4jPersistentEntity <?> entityMetaData ,
@@ -305,37 +306,42 @@ public <T> List<T> saveAll(Iterable<T> instances) {
305306 return entities .stream ().map (e -> saveImpl (e , databaseName )).collect (Collectors .toList ());
306307 }
307308
308- // we need to determine the `isNew` state of the entities before calling the id generator
309- List <Boolean > isNewIndicator = entities .stream ().map (entity ->
310- neo4jMappingContext .getPersistentEntity (entity .getClass ()).isNew (entity )
311- ).collect (Collectors .toList ());
309+ class Tuple3 <T > {
310+ T t1 ;
311+ boolean t2 ;
312+ T t3 ;
313+
314+ Tuple3 (T t1 , boolean t2 , T t3 ) {
315+ this .t1 = t1 ;
316+ this .t2 = t2 ;
317+ this .t3 = t3 ;
318+ }
319+ }
312320
313- List <T > entitiesToBeSaved = entities .stream ().map (eventSupport ::maybeCallBeforeBind )
321+ List <Tuple3 <T >> entitiesToBeSaved = entities .stream ()
322+ .map (e -> new Tuple3 <>(e , neo4jMappingContext .getPersistentEntity (e .getClass ()).isNew (e ), eventSupport .maybeCallBeforeBind (e )))
314323 .collect (Collectors .toList ());
315324
316325 // Save roots
317326 Function <T , Map <String , Object >> binderFunction = neo4jMappingContext .getRequiredBinderFunctionFor (domainClass );
318- List <Map <String , Object >> entityList = entitiesToBeSaved .stream ().map (binderFunction )
327+ List <Map <String , Object >> entityList = entitiesToBeSaved .stream ().map (h -> h . t3 ). map ( binderFunction )
319328 .collect (Collectors .toList ());
320329 ResultSummary resultSummary = neo4jClient
321330 .query (() -> renderer .render (cypherGenerator .prepareSaveOfMultipleInstancesOf (entityMetaData )))
322331 .in (databaseName )
323332 .bind (entityList ).to (Constants .NAME_OF_ENTITY_LIST_PARAM ).run ();
324333
325- // Save related
326- entitiesToBeSaved .forEach (entityToBeSaved -> {
327- int positionInList = entitiesToBeSaved .indexOf (entityToBeSaved );
328- processRelations (entityMetaData , entityToBeSaved , isNewIndicator .get (positionInList ), databaseName ,
329- entities .get (positionInList ));
330- });
331-
332334 SummaryCounters counters = resultSummary .counters ();
333335 log .debug (() -> String .format (
334336 "Created %d and deleted %d nodes, created %d and deleted %d relationships and set %d properties." ,
335337 counters .nodesCreated (), counters .nodesDeleted (), counters .relationshipsCreated (),
336338 counters .relationshipsDeleted (), counters .propertiesSet ()));
337339
338- return entitiesToBeSaved ;
340+ // Save related
341+ return entitiesToBeSaved .stream ().map (t -> {
342+ PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (t .t3 );
343+ return processRelations (entityMetaData , t .t1 , propertyAccessor , databaseName , t .t2 );
344+ }).collect (Collectors .toList ());
339345 }
340346
341347 @ Override
@@ -436,27 +442,37 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String
436442 return toExecutableQuery (preparedQuery );
437443 }
438444
439- private <T > T processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , Object parentObject ,
440- boolean isParentObjectNew , @ Nullable String inDatabase , Object parentEntity ) {
445+ /**
446+ * Starts of processing of the relationships.
447+ *
448+ * @param neo4jPersistentEntity The description of the instance to save
449+ * @param originalInstance The original parent instance. It is paramount to pass in the original instance (prior
450+ * to generating the id and prior to eventually create new instances via the property accessor,
451+ * so that we can reliable stop traversing relationships.
452+ * @param parentPropertyAccessor The property accessor of the parent, to modify the relationships
453+ * @param isParentObjectNew A flag if the parent was new
454+ */
455+ private <T > T processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , T originalInstance ,
456+ PersistentPropertyAccessor <?> parentPropertyAccessor ,
457+ @ Nullable String inDatabase , boolean isParentObjectNew ) {
441458
442- return processNestedRelations (neo4jPersistentEntity , parentObject , isParentObjectNew , inDatabase ,
443- new NestedRelationshipProcessingStateMachine (parentEntity ));
459+ return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew , inDatabase ,
460+ new NestedRelationshipProcessingStateMachine (originalInstance ));
444461 }
445462
446- private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , Object parentObject ,
463+ private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , PersistentPropertyAccessor <?> propertyAccessor ,
447464 boolean isParentObjectNew , @ Nullable String inDatabase , NestedRelationshipProcessingStateMachine stateMachine ) {
448465
449- PersistentPropertyAccessor <?> propertyAccessor = sourceEntity .getPropertyAccessor (parentObject );
450466 Object fromId = propertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
451467
452468 sourceEntity .doWithAssociations ((AssociationHandler <Neo4jPersistentProperty >) association -> {
453469
454470 // create context to bundle parameters
455- NestedRelationshipContext relationshipContext = NestedRelationshipContext .of (association , propertyAccessor ,
456- sourceEntity );
471+ NestedRelationshipContext relationshipContext = NestedRelationshipContext .of (association , propertyAccessor , sourceEntity );
457472
473+ Object rawValue = relationshipContext .getValue ();
458474 Collection <?> relatedValuesToStore = MappingSupport .unifyRelationshipValue (relationshipContext .getInverse (),
459- relationshipContext . getValue () );
475+ rawValue );
460476
461477 RelationshipDescription relationshipDescription = relationshipContext .getRelationship ();
462478 RelationshipDescription relationshipDescriptionObverse = relationshipDescription .getRelationshipObverse ();
@@ -511,22 +527,27 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
511527
512528 stateMachine .markRelationshipAsProcessed (fromId , relationshipDescription );
513529
530+ Neo4jPersistentProperty relationshipProperty = association .getInverse ();
531+
532+ RelationshipHandler relationshipHandler = RelationshipHandler .forProperty (relationshipProperty , rawValue );
533+
514534 for (Object relatedValueToStore : relatedValuesToStore ) {
515535
516536 // here a map entry is not always anymore a dynamic association
517- Object relatedNode = relationshipContext .identifyAndExtractRelationshipTargetNode (relatedValueToStore );
518- Neo4jPersistentEntity <?> targetEntity = neo4jMappingContext .getPersistentEntity (relatedNode .getClass ());
537+ Object relatedObjectBeforeCallbacks = relationshipContext .identifyAndExtractRelationshipTargetNode (relatedValueToStore );
538+ Neo4jPersistentEntity <?> targetEntity = neo4jMappingContext .getPersistentEntity (relatedObjectBeforeCallbacks .getClass ());
519539
520- boolean isEntityNew = targetEntity .isNew (relatedNode );
540+ boolean isEntityNew = targetEntity .isNew (relatedObjectBeforeCallbacks );
521541
522- relatedNode = eventSupport .maybeCallBeforeBind (relatedNode );
542+ Object newRelatedObject = eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacks );
523543
524544 Long relatedInternalId ;
525545 // No need to save values if processed
526546 if (stateMachine .hasProcessedValue (relatedValueToStore )) {
527- relatedInternalId = queryRelatedNode (relatedNode , targetEntity , inDatabase );
547+ Object newRelatedObjectForQuery = stateMachine .getProcessedAs (newRelatedObject );
548+ relatedInternalId = queryRelatedNode (newRelatedObjectForQuery , targetEntity , inDatabase );
528549 } else {
529- relatedInternalId = saveRelatedNode (relatedNode , targetEntity , inDatabase );
550+ relatedInternalId = saveRelatedNode (newRelatedObject , targetEntity , inDatabase );
530551 }
531552 stateMachine .markValueAsProcessed (relatedValueToStore );
532553
@@ -556,16 +577,27 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
556577 .setProperty (idProperty , relationshipInternalId .get ());
557578 }
558579
559- PersistentPropertyAccessor <?> targetPropertyAccessor = targetEntity .getPropertyAccessor (relatedNode );
560- // if an internal id is used this must get set to link this entity in the next iteration
580+ PersistentPropertyAccessor <?> targetPropertyAccessor = targetEntity .getPropertyAccessor (newRelatedObject );
581+ // if an internal id is used this must be set to link this entity in the next iteration
561582 if (targetEntity .isUsingInternalIds ()) {
562583 targetPropertyAccessor .setProperty (targetEntity .getRequiredIdProperty (), relatedInternalId );
584+ stateMachine .markValueAsProcessedAs (newRelatedObject , targetPropertyAccessor .getBean ());
563585 }
586+
564587 if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
565- processNestedRelations (targetEntity , targetPropertyAccessor . getBean () , isEntityNew , inDatabase , stateMachine );
588+ processNestedRelations (targetEntity , targetPropertyAccessor , isEntityNew , inDatabase , stateMachine );
566589 }
590+
591+ Object potentiallyRecreatedNewRelatedObject = MappingSupport .getRelationshipOrRelationshipPropertiesObject (neo4jMappingContext ,
592+ relationshipDescription .hasRelationshipProperties (),
593+ relationshipProperty .isDynamicAssociation (),
594+ relatedValueToStore ,
595+ targetPropertyAccessor );
596+
597+ relationshipHandler .handle (relatedValueToStore , relatedObjectBeforeCallbacks , potentiallyRecreatedNewRelatedObject );
567598 }
568599
600+ relationshipHandler .applyFinalResultToOwner (propertyAccessor );
569601 });
570602
571603 return (T ) propertyAccessor .getBean ();
@@ -720,9 +752,11 @@ private GenericQueryAndParameters createQueryAndParameters(Neo4jPersistentEntity
720752 .prepareMatchOf (entityMetaData , queryFragments .getMatchOn (), queryFragments .getCondition ())
721753 .returning (Constants .NAME_OF_SYNTHESIZED_ROOT_NODE ).build ();
722754
755+ Map <String , Object > usedParameters = new HashMap <>(parameters );
756+ usedParameters .putAll (rootNodesStatement .getParameters ());
723757 final Collection <Long > rootNodeIds = new HashSet <>((Collection <Long >) neo4jClient
724758 .query (renderer .render (rootNodesStatement )).in (getDatabaseName ())
725- .bindAll (parameters )
759+ .bindAll (usedParameters )
726760 .fetch ()
727761 .one ()
728762 .map (values -> values .get (Constants .NAME_OF_SYNTHESIZED_ROOT_NODE ))
@@ -742,6 +776,8 @@ private GenericQueryAndParameters createQueryAndParameters(Neo4jPersistentEntity
742776 .prepareMatchOf (entityMetaData , relationshipDescription , queryFragments .getMatchOn (), queryFragments .getCondition ())
743777 .returning (cypherGenerator .createReturnStatementForMatch (entityMetaData )).build ();
744778
779+ usedParameters = new HashMap <>(parameters );
780+ usedParameters .putAll (statement .getParameters ());
745781 neo4jClient .query (renderer .render (statement )).in (getDatabaseName ())
746782 .bindAll (parameters )
747783 .fetch ()
0 commit comments