@@ -267,9 +267,10 @@ private <T> T saveImpl(T instance) {
267
267
PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (entityToBeSaved );
268
268
if (entityMetaData .isUsingInternalIds ()) {
269
269
propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), optionalInternalId .get ());
270
- entityToBeSaved = propertyAccessor .getBean ();
271
270
}
272
- return processRelations (entityMetaData , entityToBeSaved , isEntityNew , instance );
271
+ processRelations (entityMetaData , instance , propertyAccessor , isEntityNew );
272
+
273
+ return propertyAccessor .getBean ();
273
274
}
274
275
275
276
private <T > DynamicLabels determineDynamicLabels (T entityToBeSaved , Neo4jPersistentEntity <?> entityMetaData ) {
@@ -318,36 +319,41 @@ public <T> List<T> saveAll(Iterable<T> instances) {
318
319
return entities .stream ().map (e -> saveImpl (e )).collect (Collectors .toList ());
319
320
}
320
321
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
+ }
325
333
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 )))
327
336
.collect (Collectors .toList ());
328
337
329
338
// Save roots
330
339
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 )
332
341
.collect (Collectors .toList ());
333
342
ResultSummary resultSummary = neo4jClient
334
343
.query (() -> renderer .render (cypherGenerator .prepareSaveOfMultipleInstancesOf (entityMetaData )))
335
344
.bind (entityList ).to (Constants .NAME_OF_ENTITY_LIST_PARAM ).run ();
336
345
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
-
344
346
SummaryCounters counters = resultSummary .counters ();
345
347
log .debug (() -> String .format (
346
348
"Created %d and deleted %d nodes, created %d and deleted %d relationships and set %d properties." ,
347
349
counters .nodesCreated (), counters .nodesDeleted (), counters .relationshipsCreated (),
348
350
counters .relationshipsDeleted (), counters .propertiesSet ()));
349
351
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 ());
351
357
}
352
358
353
359
@ Override
@@ -448,27 +454,37 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String
448
454
return toExecutableQuery (preparedQuery );
449
455
}
450
456
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 ) {
453
470
454
- return processNestedRelations (neo4jPersistentEntity , parentObject , isParentObjectNew ,
455
- new NestedRelationshipProcessingStateMachine (parentEntity ));
471
+ return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew ,
472
+ new NestedRelationshipProcessingStateMachine (originalInstance ));
456
473
}
457
474
458
- private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , Object parentObject ,
475
+ private <T > T processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , PersistentPropertyAccessor <?> propertyAccessor ,
459
476
boolean isParentObjectNew , NestedRelationshipProcessingStateMachine stateMachine ) {
460
477
461
- PersistentPropertyAccessor <?> propertyAccessor = sourceEntity .getPropertyAccessor (parentObject );
462
478
Object fromId = propertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
463
479
464
480
sourceEntity .doWithAssociations ((AssociationHandler <Neo4jPersistentProperty >) association -> {
465
481
466
482
// create context to bundle parameters
467
- NestedRelationshipContext relationshipContext = NestedRelationshipContext .of (association , propertyAccessor ,
468
- sourceEntity );
483
+ NestedRelationshipContext relationshipContext = NestedRelationshipContext .of (association , propertyAccessor , sourceEntity );
469
484
485
+ Object rawValue = relationshipContext .getValue ();
470
486
Collection <?> relatedValuesToStore = MappingSupport .unifyRelationshipValue (relationshipContext .getInverse (),
471
- relationshipContext . getValue () );
487
+ rawValue );
472
488
473
489
RelationshipDescription relationshipDescription = relationshipContext .getRelationship ();
474
490
RelationshipDescription relationshipDescriptionObverse = relationshipDescription .getRelationshipObverse ();
@@ -523,22 +539,26 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
523
539
524
540
stateMachine .markRelationshipAsProcessed (fromId , relationshipDescription );
525
541
542
+ Neo4jPersistentProperty relationshipProperty = association .getInverse ();
543
+
544
+ RelationshipHandler relationshipHandler = RelationshipHandler .forProperty (relationshipProperty , rawValue );
545
+
526
546
for (Object relatedValueToStore : relatedValuesToStore ) {
527
547
528
548
// 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 ());
531
551
532
- boolean isEntityNew = targetEntity .isNew (relatedNode );
552
+ boolean isEntityNew = targetEntity .isNew (relatedObjectBeforeCallbacks );
533
553
534
- relatedNode = eventSupport .maybeCallBeforeBind (relatedNode );
554
+ Object newRelatedObject = eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacks );
535
555
536
556
Long relatedInternalId ;
537
557
// No need to save values if processed
538
558
if (stateMachine .hasProcessedValue (relatedValueToStore )) {
539
- relatedInternalId = queryRelatedNode (relatedNode , targetEntity );
559
+ relatedInternalId = queryRelatedNode (newRelatedObject , targetEntity );
540
560
} else {
541
- relatedInternalId = saveRelatedNode (relatedNode , relationshipContext .getAssociationTargetType (),
561
+ relatedInternalId = saveRelatedNode (newRelatedObject , relationshipContext .getAssociationTargetType (),
542
562
targetEntity );
543
563
}
544
564
stateMachine .markValueAsProcessed (relatedValueToStore );
@@ -569,16 +589,26 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Obje
569
589
.setProperty (idProperty , relationshipInternalId .get ());
570
590
}
571
591
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
574
594
if (targetEntity .isUsingInternalIds ()) {
575
595
targetPropertyAccessor .setProperty (targetEntity .getRequiredIdProperty (), relatedInternalId );
576
596
}
597
+
577
598
if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
578
- processNestedRelations (targetEntity , targetPropertyAccessor . getBean () , isEntityNew , stateMachine );
599
+ processNestedRelations (targetEntity , targetPropertyAccessor , isEntityNew , stateMachine );
579
600
}
601
+
602
+ Object potentiallyRecreatedNewRelatedObject = MappingSupport .getRelationshipOrRelationshipPropertiesObject (neo4jMappingContext ,
603
+ relationshipDescription .hasRelationshipProperties (),
604
+ relationshipProperty .isDynamicAssociation (),
605
+ relatedValueToStore ,
606
+ targetPropertyAccessor );
607
+
608
+ relationshipHandler .handle (relatedValueToStore , relatedObjectBeforeCallbacks , potentiallyRecreatedNewRelatedObject );
580
609
}
581
610
611
+ relationshipHandler .applyFinalResultToOwner (propertyAccessor );
582
612
});
583
613
584
614
return (T ) propertyAccessor .getBean ();
0 commit comments