@@ -251,9 +251,10 @@ private <T> T saveImpl(T instance, @Nullable String inDatabase) {
251
251
PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (entityToBeSaved );
252
252
if (entityMetaData .isUsingInternalIds ()) {
253
253
propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), optionalInternalId .get ());
254
- entityToBeSaved = propertyAccessor .getBean ();
255
254
}
256
- return processRelations (entityMetaData , entityToBeSaved , isEntityNew , inDatabase , instance );
255
+ processRelations (entityMetaData , instance , propertyAccessor , inDatabase , isEntityNew );
256
+
257
+ return propertyAccessor .getBean ();
257
258
}
258
259
259
260
private <T > DynamicLabels determineDynamicLabels (T entityToBeSaved , Neo4jPersistentEntity <?> entityMetaData ,
@@ -305,37 +306,42 @@ public <T> List<T> saveAll(Iterable<T> instances) {
305
306
return entities .stream ().map (e -> saveImpl (e , databaseName )).collect (Collectors .toList ());
306
307
}
307
308
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
+ }
312
320
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 )))
314
323
.collect (Collectors .toList ());
315
324
316
325
// Save roots
317
326
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 )
319
328
.collect (Collectors .toList ());
320
329
ResultSummary resultSummary = neo4jClient
321
330
.query (() -> renderer .render (cypherGenerator .prepareSaveOfMultipleInstancesOf (entityMetaData )))
322
331
.in (databaseName )
323
332
.bind (entityList ).to (Constants .NAME_OF_ENTITY_LIST_PARAM ).run ();
324
333
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
-
332
334
SummaryCounters counters = resultSummary .counters ();
333
335
log .debug (() -> String .format (
334
336
"Created %d and deleted %d nodes, created %d and deleted %d relationships and set %d properties." ,
335
337
counters .nodesCreated (), counters .nodesDeleted (), counters .relationshipsCreated (),
336
338
counters .relationshipsDeleted (), counters .propertiesSet ()));
337
339
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 ());
339
345
}
340
346
341
347
@ Override
@@ -436,27 +442,37 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String
436
442
return toExecutableQuery (preparedQuery );
437
443
}
438
444
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 ) {
441
458
442
- return processNestedRelations (neo4jPersistentEntity , parentObject , isParentObjectNew , inDatabase ,
443
- new NestedRelationshipProcessingStateMachine (parentEntity ));
459
+ return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew , inDatabase ,
460
+ new NestedRelationshipProcessingStateMachine (originalInstance ));
444
461
}
445
462
446
- private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , Object parentObject ,
463
+ private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , PersistentPropertyAccessor <?> propertyAccessor ,
447
464
boolean isParentObjectNew , @ Nullable String inDatabase , NestedRelationshipProcessingStateMachine stateMachine ) {
448
465
449
- PersistentPropertyAccessor <?> propertyAccessor = sourceEntity .getPropertyAccessor (parentObject );
450
466
Object fromId = propertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
451
467
452
468
sourceEntity .doWithAssociations ((AssociationHandler <Neo4jPersistentProperty >) association -> {
453
469
454
470
// create context to bundle parameters
455
- NestedRelationshipContext relationshipContext = NestedRelationshipContext .of (association , propertyAccessor ,
456
- sourceEntity );
471
+ NestedRelationshipContext relationshipContext = NestedRelationshipContext .of (association , propertyAccessor , sourceEntity );
457
472
473
+ Object rawValue = relationshipContext .getValue ();
458
474
Collection <?> relatedValuesToStore = MappingSupport .unifyRelationshipValue (relationshipContext .getInverse (),
459
- relationshipContext . getValue () );
475
+ rawValue );
460
476
461
477
RelationshipDescription relationshipDescription = relationshipContext .getRelationship ();
462
478
RelationshipDescription relationshipDescriptionObverse = relationshipDescription .getRelationshipObverse ();
@@ -511,22 +527,27 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
511
527
512
528
stateMachine .markRelationshipAsProcessed (fromId , relationshipDescription );
513
529
530
+ Neo4jPersistentProperty relationshipProperty = association .getInverse ();
531
+
532
+ RelationshipHandler relationshipHandler = RelationshipHandler .forProperty (relationshipProperty , rawValue );
533
+
514
534
for (Object relatedValueToStore : relatedValuesToStore ) {
515
535
516
536
// 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 ());
519
539
520
- boolean isEntityNew = targetEntity .isNew (relatedNode );
540
+ boolean isEntityNew = targetEntity .isNew (relatedObjectBeforeCallbacks );
521
541
522
- relatedNode = eventSupport .maybeCallBeforeBind (relatedNode );
542
+ Object newRelatedObject = eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacks );
523
543
524
544
Long relatedInternalId ;
525
545
// No need to save values if processed
526
546
if (stateMachine .hasProcessedValue (relatedValueToStore )) {
527
- relatedInternalId = queryRelatedNode (relatedNode , targetEntity , inDatabase );
547
+ Object newRelatedObjectForQuery = stateMachine .getProcessedAs (newRelatedObject );
548
+ relatedInternalId = queryRelatedNode (newRelatedObjectForQuery , targetEntity , inDatabase );
528
549
} else {
529
- relatedInternalId = saveRelatedNode (relatedNode , targetEntity , inDatabase );
550
+ relatedInternalId = saveRelatedNode (newRelatedObject , targetEntity , inDatabase );
530
551
}
531
552
stateMachine .markValueAsProcessed (relatedValueToStore );
532
553
@@ -556,16 +577,27 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
556
577
.setProperty (idProperty , relationshipInternalId .get ());
557
578
}
558
579
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
561
582
if (targetEntity .isUsingInternalIds ()) {
562
583
targetPropertyAccessor .setProperty (targetEntity .getRequiredIdProperty (), relatedInternalId );
584
+ stateMachine .markValueAsProcessedAs (newRelatedObject , targetPropertyAccessor .getBean ());
563
585
}
586
+
564
587
if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
565
- processNestedRelations (targetEntity , targetPropertyAccessor . getBean () , isEntityNew , inDatabase , stateMachine );
588
+ processNestedRelations (targetEntity , targetPropertyAccessor , isEntityNew , inDatabase , stateMachine );
566
589
}
590
+
591
+ Object potentiallyRecreatedNewRelatedObject = MappingSupport .getRelationshipOrRelationshipPropertiesObject (neo4jMappingContext ,
592
+ relationshipDescription .hasRelationshipProperties (),
593
+ relationshipProperty .isDynamicAssociation (),
594
+ relatedValueToStore ,
595
+ targetPropertyAccessor );
596
+
597
+ relationshipHandler .handle (relatedValueToStore , relatedObjectBeforeCallbacks , potentiallyRecreatedNewRelatedObject );
567
598
}
568
599
600
+ relationshipHandler .applyFinalResultToOwner (propertyAccessor );
569
601
});
570
602
571
603
return (T ) propertyAccessor .getBean ();
@@ -720,9 +752,11 @@ private GenericQueryAndParameters createQueryAndParameters(Neo4jPersistentEntity
720
752
.prepareMatchOf (entityMetaData , queryFragments .getMatchOn (), queryFragments .getCondition ())
721
753
.returning (Constants .NAME_OF_SYNTHESIZED_ROOT_NODE ).build ();
722
754
755
+ Map <String , Object > usedParameters = new HashMap <>(parameters );
756
+ usedParameters .putAll (rootNodesStatement .getParameters ());
723
757
final Collection <Long > rootNodeIds = new HashSet <>((Collection <Long >) neo4jClient
724
758
.query (renderer .render (rootNodesStatement )).in (getDatabaseName ())
725
- .bindAll (parameters )
759
+ .bindAll (usedParameters )
726
760
.fetch ()
727
761
.one ()
728
762
.map (values -> values .get (Constants .NAME_OF_SYNTHESIZED_ROOT_NODE ))
@@ -742,6 +776,8 @@ private GenericQueryAndParameters createQueryAndParameters(Neo4jPersistentEntity
742
776
.prepareMatchOf (entityMetaData , relationshipDescription , queryFragments .getMatchOn (), queryFragments .getCondition ())
743
777
.returning (cypherGenerator .createReturnStatementForMatch (entityMetaData )).build ();
744
778
779
+ usedParameters = new HashMap <>(parameters );
780
+ usedParameters .putAll (statement .getParameters ());
745
781
neo4jClient .query (renderer .render (statement )).in (getDatabaseName ())
746
782
.bindAll (parameters )
747
783
.fetch ()
0 commit comments