@@ -232,13 +232,25 @@ private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, Obj
232
232
@ Override
233
233
public <T > Mono <T > save (T instance ) {
234
234
235
- return getDatabaseName ().flatMap (databaseName -> saveImpl (instance , databaseName .getValue ()));
235
+ return getDatabaseName ().flatMap (databaseName -> saveImpl (instance , databaseName .getValue (), null ));
236
236
}
237
237
238
- private <T > Mono <T > saveImpl (T instance , @ Nullable String inDatabase ) {
238
+ private <T > Mono <T > saveImpl (T instance , @ Nullable String inDatabase , @ Nullable NestedRelationshipProcessingStateMachine stateMachine ) {
239
+
240
+ if (stateMachine != null && stateMachine .hasProcessedValue (instance )) {
241
+ return Mono .just (instance );
242
+ }
239
243
240
244
Neo4jPersistentEntity entityMetaData = neo4jMappingContext .getPersistentEntity (instance .getClass ());
241
245
boolean isNewEntity = entityMetaData .isNew (instance );
246
+
247
+ NestedRelationshipProcessingStateMachine finalStateMachine ;
248
+ if (stateMachine == null ) {
249
+ finalStateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext );
250
+ } else {
251
+ finalStateMachine = stateMachine ;
252
+ }
253
+
242
254
return Mono .just (instance ).flatMap (eventSupport ::maybeCallBeforeBind )
243
255
.flatMap (entityToBeSaved -> determineDynamicLabels (entityToBeSaved , entityMetaData , inDatabase )).flatMap (t -> {
244
256
T entityToBeSaved = t .getT1 ();
@@ -258,12 +270,13 @@ private <T> Mono<T> saveImpl(T instance, @Nullable String inDatabase) {
258
270
259
271
PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (entityToBeSaved );
260
272
return idMono .doOnNext (newOrUpdatedNode -> {
261
- if (entityMetaData .isUsingInternalIds ()) {
262
- propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), newOrUpdatedNode .id ());
263
- }
264
- TemplateSupport .updateVersionPropertyIfPossible (entityMetaData , propertyAccessor , newOrUpdatedNode );
265
- }).map (Entity ::id )
266
- .flatMap (internalId -> processRelations (entityMetaData , instance , internalId , propertyAccessor , inDatabase , isNewEntity ));
273
+ if (entityMetaData .isUsingInternalIds ()) {
274
+ propertyAccessor .setProperty (entityMetaData .getRequiredIdProperty (), newOrUpdatedNode .id ());
275
+ }
276
+ TemplateSupport .updateVersionPropertyIfPossible (entityMetaData , propertyAccessor , newOrUpdatedNode );
277
+ finalStateMachine .markValueAsProcessed (instance , newOrUpdatedNode .id ());
278
+ }).map (Entity ::id )
279
+ .flatMap (internalId -> processRelations (entityMetaData , propertyAccessor , isNewEntity , finalStateMachine , inDatabase ));
267
280
});
268
281
}
269
282
@@ -312,8 +325,9 @@ public <T> Flux<T> saveAll(Iterable<T> instances) {
312
325
|| entityMetaData .getDynamicLabelsProperty ().isPresent ()) {
313
326
log .debug ("Saving entities using single statements." );
314
327
328
+ NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext );
315
329
return getDatabaseName ().flatMapMany (
316
- databaseName -> Flux .fromIterable (entities ).flatMap (e -> this .saveImpl (e , databaseName .getValue ())));
330
+ databaseName -> Flux .fromIterable (entities ).concatMap (e -> this .saveImpl (e , databaseName .getValue (), stateMachine )));
317
331
}
318
332
319
333
Function <T , Map <String , Object >> binderFunction = neo4jMappingContext .getRequiredBinderFunctionFor (domainClass );
@@ -343,8 +357,7 @@ public <T> Flux<T> saveAll(Iterable<T> instances) {
343
357
Neo4jPersistentProperty idProperty = entityMetaData .getRequiredIdProperty ();
344
358
Object id = convertIdValues (idProperty , propertyAccessor .getProperty (idProperty ));
345
359
Long internalId = idToInternalIdMapping .get (id );
346
- return processRelations (entityMetaData , t .getT1 (), internalId ,
347
- propertyAccessor , databaseName .getValue (), t .getT2 ());
360
+ return processRelations (entityMetaData , propertyAccessor , t .getT2 (), new NestedRelationshipProcessingStateMachine (neo4jMappingContext , t .getT1 (), internalId ), databaseName .getValue ());
348
361
}))
349
362
));
350
363
}
@@ -445,8 +458,8 @@ private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType,
445
458
private <T > Mono <ExecutableQuery <T >> createExecutableQuery (Class <T > domainType ,
446
459
QueryFragmentsAndParameters queryFragmentsAndParameters ) {
447
460
448
- Neo4jPersistentEntity <?> entityMetaData = neo4jMappingContext .getPersistentEntity (domainType );
449
461
QueryFragmentsAndParameters .QueryFragments queryFragments = queryFragmentsAndParameters .getQueryFragments ();
462
+ Neo4jPersistentEntity <?> entityMetaData = (Neo4jPersistentEntity <?>) queryFragmentsAndParameters .getNodeDescription ();
450
463
451
464
boolean containsPossibleCircles = entityMetaData != null && entityMetaData .containsPossibleCircles (queryFragments ::includeField );
452
465
if (containsPossibleCircles && !queryFragments .isScalarValueReturn ()) {
@@ -574,24 +587,24 @@ Publisher<Tuple2<Collection<Long>, Collection<Long>>>> iterateAndMapNextLevel(
574
587
* Starts of processing of the relationships.
575
588
*
576
589
* @param neo4jPersistentEntity The description of the instance to save
577
- * @param originalInstance The original parent instance. It is paramount to pass in the original instance (prior
578
- * to generating the id and prior to eventually create new instances via the property accessor,
579
- * so that we can reliable stop traversing relationships.
580
590
* @param parentPropertyAccessor The property accessor of the parent, to modify the relationships
581
591
* @param isParentObjectNew A flag if the parent was new
592
+ * @param stateMachine Statemachine containing process data
593
+ * @param inDatabase Optional target database
582
594
* @param <T> The type of the entity to save
583
595
* @return A mono representing the whole stream of save operations.
584
596
*/
585
- private <T > Mono <T > processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity , T originalInstance ,
586
- Long internalId , PersistentPropertyAccessor <?> parentPropertyAccessor ,
587
- @ Nullable String inDatabase , boolean isParentObjectNew ) {
597
+ private <T > Mono <T > processRelations (Neo4jPersistentEntity <?> neo4jPersistentEntity ,
598
+ PersistentPropertyAccessor <?> parentPropertyAccessor ,
599
+ boolean isParentObjectNew , NestedRelationshipProcessingStateMachine stateMachine ,
600
+ @ Nullable String inDatabase ) {
588
601
589
- return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew , inDatabase ,
590
- new NestedRelationshipProcessingStateMachine ( originalInstance , internalId ) );
602
+ return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew , stateMachine ,
603
+ inDatabase );
591
604
}
592
605
593
606
private <T > Mono <T > processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , PersistentPropertyAccessor <?> parentPropertyAccessor ,
594
- boolean isParentObjectNew , @ Nullable String inDatabase , NestedRelationshipProcessingStateMachine stateMachine ) {
607
+ boolean isParentObjectNew , NestedRelationshipProcessingStateMachine stateMachine , @ Nullable String inDatabase ) {
595
608
596
609
Object fromId = parentPropertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
597
610
List <Mono <Void >> relationshipDeleteMonos = new ArrayList <>();
@@ -665,36 +678,53 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
665
678
Flux <RelationshipHandler > relationshipCreation = Flux .fromIterable (relatedValuesToStore ).concatMap (relatedValueToStore -> {
666
679
667
680
Object relatedObjectBeforeCallbacksApplied = relationshipContext .identifyAndExtractRelationshipTargetNode (relatedValueToStore );
668
- return Mono .deferContextual (ctx -> eventSupport
669
- .maybeCallBeforeBind (relatedObjectBeforeCallbacksApplied )
681
+ return Mono .deferContextual (ctx ->
682
+
683
+ (stateMachine .hasProcessedValue (relatedObjectBeforeCallbacksApplied )
684
+ ? Mono .just (stateMachine .getProcessedAs (relatedObjectBeforeCallbacksApplied ))
685
+ : eventSupport .maybeCallBeforeBind (relatedObjectBeforeCallbacksApplied ))
686
+
670
687
.flatMap (newRelatedObject -> {
671
688
Neo4jPersistentEntity <?> targetEntity = neo4jMappingContext .getPersistentEntity (relatedObjectBeforeCallbacksApplied .getClass ());
672
689
673
- Mono <Tuple2 <Long , Long >> queryOrSave ;
690
+ Mono <Tuple2 <Long [] , Long [] >> queryOrSave ;
674
691
long noVersion = Long .MIN_VALUE ;
692
+ long noId = Long .MIN_VALUE ;
675
693
if (stateMachine .hasProcessedValue (relatedValueToStore )) {
676
- queryOrSave = Mono .just (stateMachine .getInternalId (relatedObjectBeforeCallbacksApplied ))
677
- .map (id -> Tuples .of (id , noVersion ));
694
+ queryOrSave = Mono .just (new Long [] { stateMachine .getInternalId (relatedObjectBeforeCallbacksApplied ) } )
695
+ .map (id -> Tuples .of (id , new Long [ 1 ] ));
678
696
} else {
679
697
queryOrSave = saveRelatedNode (newRelatedObject , targetEntity , inDatabase )
680
- .map (entity -> Tuples .of (entity .id (), targetEntity .hasVersionProperty () ?
681
- entity .get (targetEntity .getVersionProperty ().getPropertyName ())
682
- .asLong () :
683
- noVersion ));
698
+ .doOnNext (entity -> {
699
+ stateMachine .markValueAsProcessed (relatedValueToStore , entity .id ());
700
+ })
701
+ .map (entity -> {
702
+ Long version = targetEntity .hasVersionProperty () ?
703
+ entity .get (targetEntity .getVersionProperty ().getPropertyName ()).asLong () :
704
+ null ;
705
+ return Tuples .of (
706
+ new Long [] { entity .id () },
707
+ new Long [] { version });
708
+ });
684
709
}
685
710
686
711
return queryOrSave .flatMap (idAndVersion -> {
687
- long relatedInternalId = idAndVersion .getT1 ();
688
- stateMachine .markValueAsProcessed (relatedValueToStore , relatedInternalId );
712
+ Long relatedInternalId = idAndVersion .getT1 ()[0 ];
689
713
// if an internal id is used this must be set to link this entity in the next iteration
690
714
PersistentPropertyAccessor <?> targetPropertyAccessor = targetEntity .getPropertyAccessor (newRelatedObject );
691
715
if (targetEntity .isUsingInternalIds ()) {
692
- targetPropertyAccessor .setProperty (targetEntity .getRequiredIdProperty (), relatedInternalId );
693
- stateMachine .markValueAsProcessedAs (newRelatedObject , targetPropertyAccessor .getBean ());
716
+ Neo4jPersistentProperty requiredIdProperty = targetEntity .getRequiredIdProperty ();
717
+ if (relatedInternalId == null
718
+ && targetPropertyAccessor .getProperty (requiredIdProperty ) != null ) {
719
+ relatedInternalId = (Long ) targetPropertyAccessor .getProperty (requiredIdProperty );
720
+ } else if (targetPropertyAccessor .getProperty (requiredIdProperty ) == null ) {
721
+ targetPropertyAccessor .setProperty (requiredIdProperty , relatedInternalId );
722
+ }
694
723
}
695
- if (targetEntity .hasVersionProperty () && idAndVersion .getT2 () != noVersion ) {
696
- targetPropertyAccessor .setProperty (targetEntity .getVersionProperty (), idAndVersion .getT2 ());
724
+ if (targetEntity .hasVersionProperty () && idAndVersion .getT2 ()[ 0 ] != null ) {
725
+ targetPropertyAccessor .setProperty (targetEntity .getVersionProperty (), idAndVersion .getT2 ()[ 0 ] );
697
726
}
727
+ stateMachine .markValueAsProcessedAs (relatedObjectBeforeCallbacksApplied , targetPropertyAccessor .getBean ());
698
728
stateMachine .markRelationshipAsProcessed (relatedInternalId , relationshipDescription .getRelationshipObverse ());
699
729
700
730
Object idValue = idProperty != null
@@ -726,7 +756,7 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
726
756
727
757
Mono <Object > nestedRelationshipsSignal = null ;
728
758
if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
729
- nestedRelationshipsSignal = processNestedRelations (targetEntity , targetPropertyAccessor , targetEntity .isNew (newRelatedObject ), inDatabase , stateMachine );
759
+ nestedRelationshipsSignal = processNestedRelations (targetEntity , targetPropertyAccessor , targetEntity .isNew (newRelatedObject ), stateMachine , inDatabase );
730
760
}
731
761
732
762
Mono <Object > getRelationshipOrRelationshipPropertiesObject = Mono .fromSupplier (() -> MappingSupport .getRelationshipOrRelationshipPropertiesObject (
0 commit comments