@@ -267,9 +267,10 @@ private <T> T saveImpl(T instance) {
267267 PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (entityToBeSaved );
268268 if (entityMetaData .isUsingInternalIds ()) {
269269 propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), optionalInternalId .get ());
270- entityToBeSaved = propertyAccessor .getBean ();
271270 }
272- return processRelations (entityMetaData , entityToBeSaved , isEntityNew , instance );
271+ processRelations (entityMetaData , instance , propertyAccessor , isEntityNew );
272+
273+ return propertyAccessor .getBean ();
273274 }
274275
275276 private <T > DynamicLabels determineDynamicLabels (T entityToBeSaved , Neo4jPersistentEntity <?> entityMetaData ) {
@@ -318,36 +319,41 @@ public <T> List<T> saveAll(Iterable<T> instances) {
318319 return entities .stream ().map (e -> saveImpl (e )).collect (Collectors .toList ());
319320 }
320321
321- // we need to determine the `isNew` state of the entities before calling the id generator
322- List <Boolean > isNewIndicator = entities .stream ().map (entity ->
323- neo4jMappingContext .getPersistentEntity (entity .getClass ()).isNew (entity )
324- ).collect (Collectors .toList ());
322+ class Tuple3 <T > {
323+ T t1 ;
324+ boolean t2 ;
325+ T t3 ;
326+
327+ Tuple3 (T t1 , boolean t2 , T t3 ) {
328+ this .t1 = t1 ;
329+ this .t2 = t2 ;
330+ this .t3 = t3 ;
331+ }
332+ }
325333
326- List <T > entitiesToBeSaved = entities .stream ().map (eventSupport ::maybeCallBeforeBind )
334+ List <Tuple3 <T >> entitiesToBeSaved = entities .stream ()
335+ .map (e -> new Tuple3 <>(e , neo4jMappingContext .getPersistentEntity (e .getClass ()).isNew (e ), eventSupport .maybeCallBeforeBind (e )))
327336 .collect (Collectors .toList ());
328337
329338 // Save roots
330339 Function <T , Map <String , Object >> binderFunction = neo4jMappingContext .getRequiredBinderFunctionFor (domainClass );
331- List <Map <String , Object >> entityList = entitiesToBeSaved .stream ().map (binderFunction )
340+ List <Map <String , Object >> entityList = entitiesToBeSaved .stream ().map (h -> h . t3 ). map ( binderFunction )
332341 .collect (Collectors .toList ());
333342 ResultSummary resultSummary = neo4jClient
334343 .query (() -> renderer .render (cypherGenerator .prepareSaveOfMultipleInstancesOf (entityMetaData )))
335344 .bind (entityList ).to (Constants .NAME_OF_ENTITY_LIST_PARAM ).run ();
336345
337- // Save related
338- entitiesToBeSaved .forEach (entityToBeSaved -> {
339- int positionInList = entitiesToBeSaved .indexOf (entityToBeSaved );
340- processRelations (entityMetaData , entityToBeSaved , isNewIndicator .get (positionInList ),
341- entities .get (positionInList ));
342- });
343-
344346 SummaryCounters counters = resultSummary .counters ();
345347 log .debug (() -> String .format (
346348 "Created %d and deleted %d nodes, created %d and deleted %d relationships and set %d properties." ,
347349 counters .nodesCreated (), counters .nodesDeleted (), counters .relationshipsCreated (),
348350 counters .relationshipsDeleted (), counters .propertiesSet ()));
349351
350- return entitiesToBeSaved ;
352+ // Save related
353+ return entitiesToBeSaved .stream ().map (t -> {
354+ PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (t .t3 );
355+ return processRelations (entityMetaData , t .t1 , propertyAccessor , t .t2 );
356+ }).collect (Collectors .toList ());
351357 }
352358
353359 @ Override
@@ -448,27 +454,37 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String
448454 return toExecutableQuery (preparedQuery );
449455 }
450456
451- private <T > T processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , Object parentObject ,
452- boolean isParentObjectNew , Object parentEntity ) {
457+ /**
458+ * Starts of processing of the relationships.
459+ *
460+ * @param neo4jPersistentEntity The description of the instance to save
461+ * @param originalInstance The original parent instance. It is paramount to pass in the original instance (prior
462+ * to generating the id and prior to eventually create new instances via the property accessor,
463+ * so that we can reliable stop traversing relationships.
464+ * @param parentPropertyAccessor The property accessor of the parent, to modify the relationships
465+ * @param isParentObjectNew A flag if the parent was new
466+ */
467+ private <T > T processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , T originalInstance ,
468+ PersistentPropertyAccessor <?> parentPropertyAccessor ,
469+ boolean isParentObjectNew ) {
453470
454- return processNestedRelations (neo4jPersistentEntity , parentObject , isParentObjectNew ,
455- new NestedRelationshipProcessingStateMachine (parentEntity ));
471+ return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew ,
472+ new NestedRelationshipProcessingStateMachine (originalInstance ));
456473 }
457474
458- private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , Object parentObject ,
475+ private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , PersistentPropertyAccessor <?> propertyAccessor ,
459476 boolean isParentObjectNew , NestedRelationshipProcessingStateMachine stateMachine ) {
460477
461- PersistentPropertyAccessor <?> propertyAccessor = sourceEntity .getPropertyAccessor (parentObject );
462478 Object fromId = propertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
463479
464480 sourceEntity .doWithAssociations ((AssociationHandler <Neo4jPersistentProperty >) association -> {
465481
466482 // create context to bundle parameters
467- NestedRelationshipContext relationshipContext = NestedRelationshipContext .of (association , propertyAccessor ,
468- sourceEntity );
483+ NestedRelationshipContext relationshipContext = NestedRelationshipContext .of (association , propertyAccessor , sourceEntity );
469484
485+ Object rawValue = relationshipContext .getValue ();
470486 Collection <?> relatedValuesToStore = MappingSupport .unifyRelationshipValue (relationshipContext .getInverse (),
471- relationshipContext . getValue () );
487+ rawValue );
472488
473489 RelationshipDescription relationshipDescription = relationshipContext .getRelationship ();
474490 RelationshipDescription relationshipDescriptionObverse = relationshipDescription .getRelationshipObverse ();
@@ -523,22 +539,26 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
523539
524540 stateMachine .markRelationshipAsProcessed (fromId , relationshipDescription );
525541
542+ Neo4jPersistentProperty relationshipProperty = association .getInverse ();
543+
544+ RelationshipHandler relationshipHandler = RelationshipHandler .forProperty (relationshipProperty , rawValue );
545+
526546 for (Object relatedValueToStore : relatedValuesToStore ) {
527547
528548 // here a map entry is not always anymore a dynamic association
529- Object relatedNode = relationshipContext .identifyAndExtractRelationshipTargetNode (relatedValueToStore );
530- Neo4jPersistentEntity <?> targetEntity = neo4jMappingContext .getPersistentEntity (relatedNode .getClass ());
549+ Object relatedObjectBeforeCallbacks = relationshipContext .identifyAndExtractRelationshipTargetNode (relatedValueToStore );
550+ Neo4jPersistentEntity <?> targetEntity = neo4jMappingContext .getPersistentEntity (relatedObjectBeforeCallbacks .getClass ());
531551
532- boolean isEntityNew = targetEntity .isNew (relatedNode );
552+ boolean isEntityNew = targetEntity .isNew (relatedObjectBeforeCallbacks );
533553
534- relatedNode = eventSupport .maybeCallBeforeBind (relatedNode );
554+ Object newRelatedObject = eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacks );
535555
536556 Long relatedInternalId ;
537557 // No need to save values if processed
538558 if (stateMachine .hasProcessedValue (relatedValueToStore )) {
539- relatedInternalId = queryRelatedNode (relatedNode , targetEntity );
559+ relatedInternalId = queryRelatedNode (newRelatedObject , targetEntity );
540560 } else {
541- relatedInternalId = saveRelatedNode (relatedNode , relationshipContext .getAssociationTargetType (),
561+ relatedInternalId = saveRelatedNode (newRelatedObject , relationshipContext .getAssociationTargetType (),
542562 targetEntity );
543563 }
544564 stateMachine .markValueAsProcessed (relatedValueToStore );
@@ -569,16 +589,26 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
569589 .setProperty (idProperty , relationshipInternalId .get ());
570590 }
571591
572- PersistentPropertyAccessor <?> targetPropertyAccessor = targetEntity .getPropertyAccessor (relatedNode );
573- // if an internal id is used this must get set to link this entity in the next iteration
592+ PersistentPropertyAccessor <?> targetPropertyAccessor = targetEntity .getPropertyAccessor (newRelatedObject );
593+ // if an internal id is used this must be set to link this entity in the next iteration
574594 if (targetEntity .isUsingInternalIds ()) {
575595 targetPropertyAccessor .setProperty (targetEntity .getRequiredIdProperty (), relatedInternalId );
576596 }
597+
577598 if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
578- processNestedRelations (targetEntity , targetPropertyAccessor . getBean () , isEntityNew , stateMachine );
599+ processNestedRelations (targetEntity , targetPropertyAccessor , isEntityNew , stateMachine );
579600 }
601+
602+ Object potentiallyRecreatedNewRelatedObject = MappingSupport .getRelationshipOrRelationshipPropertiesObject (neo4jMappingContext ,
603+ relationshipDescription .hasRelationshipProperties (),
604+ relationshipProperty .isDynamicAssociation (),
605+ relatedValueToStore ,
606+ targetPropertyAccessor );
607+
608+ relationshipHandler .handle (relatedValueToStore , relatedObjectBeforeCallbacks , potentiallyRecreatedNewRelatedObject );
580609 }
581610
611+ relationshipHandler .applyFinalResultToOwner (propertyAccessor );
582612 });
583613
584614 return (T ) propertyAccessor .getBean ();
0 commit comments