@@ -418,19 +418,25 @@ <T, R> Flux<R> doSave(Iterable<R> instances, Class<T> domainType) {
418
418
getProjectionFactory (), neo4jMappingContext );
419
419
420
420
NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine (neo4jMappingContext );
421
+ Collection <Object > knownRelationshipsIds = new HashSet <>();
421
422
EntityFromDtoInstantiatingConverter <T > converter = new EntityFromDtoInstantiatingConverter <>(domainType , neo4jMappingContext );
422
423
return Flux .fromIterable (instances )
423
424
.concatMap (instance -> {
424
425
T domainObject = converter .convert (instance );
425
426
426
427
@ SuppressWarnings ("unchecked" )
427
- Mono <R > result = transactionalOperator .transactional (saveImpl (domainObject , pps , stateMachine )
428
+ Mono <R > result = transactionalOperator .transactional (saveImpl (domainObject , pps , stateMachine , knownRelationshipsIds )
428
429
.map (savedEntity -> (R ) new DtoInstantiatingConverter (resultType , neo4jMappingContext ).convertDirectly (savedEntity )));
429
430
return result ;
430
431
});
431
432
}
432
433
434
+
433
435
private <T > Mono <T > saveImpl (T instance , @ Nullable Collection <PropertyFilter .ProjectedPath > includedProperties , @ Nullable NestedRelationshipProcessingStateMachine stateMachine ) {
436
+ return saveImpl (instance , includedProperties , stateMachine , new HashSet <>());
437
+ }
438
+
439
+ private <T > Mono <T > saveImpl (T instance , @ Nullable Collection <PropertyFilter .ProjectedPath > includedProperties , @ Nullable NestedRelationshipProcessingStateMachine stateMachine , Collection <Object > knownRelationshipsIds ) {
434
440
435
441
if (stateMachine != null && stateMachine .hasProcessedValue (instance )) {
436
442
return Mono .just (instance );
@@ -479,7 +485,7 @@ private <T> Mono<T> saveImpl(T instance, @Nullable Collection<PropertyFilter.Pro
479
485
TemplateSupport .updateVersionPropertyIfPossible (entityMetaData , propertyAccessor , newOrUpdatedNode );
480
486
finalStateMachine .markEntityAsProcessed (instance , elementId );
481
487
}).map (IdentitySupport ::getElementId )
482
- .flatMap (internalId -> processRelations (entityMetaData , propertyAccessor , isNewEntity , finalStateMachine , binderFunction .filter ));
488
+ .flatMap (internalId -> processRelations (entityMetaData , propertyAccessor , isNewEntity , finalStateMachine , knownRelationshipsIds , binderFunction .filter ));
483
489
});
484
490
}
485
491
@@ -594,7 +600,7 @@ private <T> Flux<T> saveAllImpl(Iterable<T> instances, @Nullable Collection<Prop
594
600
Function <T , Map <String , Object >> binderFunction = TemplateSupport .createAndApplyPropertyFilter (
595
601
pps , entityMetaData ,
596
602
neo4jMappingContext .getRequiredBinderFunctionFor ((Class <T >) domainClass ));
597
- return Flux .fromIterable (entities )
603
+ return ( Flux < T >) Flux . deferContextual (( ctx ) -> Flux .fromIterable (entities )
598
604
// Map all entities into a tuple <Original, OriginalWasNew>
599
605
.map (e -> Tuples .of (e , entityMetaData .isNew (e )))
600
606
// Map that tuple into a tuple <<Original, OriginalWasNew>, PotentiallyModified>
@@ -618,11 +624,16 @@ private <T> Flux<T> saveAllImpl(Iterable<T> instances, @Nullable Collection<Prop
618
624
.concatMap (t -> {
619
625
PersistentPropertyAccessor <T > propertyAccessor = entityMetaData .getPropertyAccessor (t .getT3 ());
620
626
Neo4jPersistentProperty idProperty = entityMetaData .getRequiredIdProperty ();
621
- Object id = convertIdValues ( idProperty , propertyAccessor . getProperty ( idProperty ));
622
- String internalId = idToInternalIdMapping .get (id );
623
- return processRelations ( entityMetaData , propertyAccessor , t . getT2 (), new NestedRelationshipProcessingStateMachine ( neo4jMappingContext , t . getT1 (), internalId ),
627
+ return processRelations ( entityMetaData , propertyAccessor , t . getT2 (),
628
+ ctx .get ("stateMachine" ),
629
+ ctx . get ( "knownRelIds" ),
624
630
TemplateSupport .computeIncludePropertyPredicate (pps , entityMetaData ));
625
631
}))
632
+ ))
633
+ .contextWrite (ctx ->
634
+ ctx
635
+ .put ("stateMachine" , new NestedRelationshipProcessingStateMachine (neo4jMappingContext , null , null ))
636
+ .put ("knownRelIds" , new HashSet <>())
626
637
);
627
638
}
628
639
@@ -878,16 +889,19 @@ private <T> Mono<T> processRelations(
878
889
PersistentPropertyAccessor <?> parentPropertyAccessor ,
879
890
boolean isParentObjectNew ,
880
891
NestedRelationshipProcessingStateMachine stateMachine ,
892
+ Collection <Object > knownRelationshipsIds ,
881
893
PropertyFilter includeProperty
882
894
) {
883
895
884
896
PropertyFilter .RelaxedPropertyPath startingPropertyPath = PropertyFilter .RelaxedPropertyPath .withRootType (neo4jPersistentEntity .getUnderlyingClass ());
885
897
return processNestedRelations (neo4jPersistentEntity , parentPropertyAccessor , isParentObjectNew ,
886
- stateMachine , includeProperty , startingPropertyPath );
898
+ stateMachine , knownRelationshipsIds , includeProperty , startingPropertyPath );
887
899
}
888
900
889
901
private <T > Mono <T > processNestedRelations (Neo4jPersistentEntity <?> sourceEntity , PersistentPropertyAccessor <?> parentPropertyAccessor ,
890
- boolean isParentObjectNew , NestedRelationshipProcessingStateMachine stateMachine , PropertyFilter includeProperty , PropertyFilter .RelaxedPropertyPath previousPath ) {
902
+ boolean isParentObjectNew , NestedRelationshipProcessingStateMachine stateMachine ,
903
+ Collection <Object > knownRelationshipsIds ,
904
+ PropertyFilter includeProperty , PropertyFilter .RelaxedPropertyPath previousPath ) {
891
905
892
906
Object fromId = parentPropertyAccessor .getProperty (sourceEntity .getRequiredIdProperty ());
893
907
List <Mono <Void >> relationshipDeleteMonos = new ArrayList <>();
@@ -932,7 +946,6 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
932
946
boolean canUseElementId = TemplateSupport .rendererRendersElementId (renderer );
933
947
if (!isParentObjectNew && !stateMachine .hasProcessedRelationship (fromId , relationshipDescription )) {
934
948
935
- List <Object > knownRelationshipsIds = new ArrayList <>();
936
949
if (idProperty != null ) {
937
950
for (Object relatedValueToStore : relatedValuesToStore ) {
938
951
if (relatedValueToStore == null ) {
@@ -1021,7 +1034,7 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
1021
1034
TemplateSupport .updateVersionPropertyIfPossible (targetEntity , targetPropertyAccessor , savedEntity );
1022
1035
}
1023
1036
stateMachine .markAsAliased (relatedObjectBeforeCallbacksApplied , targetPropertyAccessor .getBean ());
1024
- stateMachine .markRelationshipAsProcessed (possibleInternalLongId == null ? relatedInternalId : possibleInternalLongId ,
1037
+ stateMachine .markRelationshipAsProcessed (possibleInternalLongId == null ? relatedInternalId : possibleInternalLongId ,
1025
1038
relationshipDescription .getRelationshipObverse ());
1026
1039
1027
1040
Object idValue = idProperty != null
@@ -1037,43 +1050,63 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
1037
1050
properties .put (Constants .FROM_ID_PARAMETER_NAME , convertIdValues (sourceEntity .getRequiredIdProperty (), fromId ));
1038
1051
properties .put (Constants .TO_ID_PARAMETER_NAME , relatedInternalId );
1039
1052
properties .put (Constants .NAME_OF_KNOWN_RELATIONSHIP_PARAM , idValue );
1053
+ var update = true ;
1054
+ if (!relationshipDescription .isDynamic () && relationshipDescription .hasRelationshipProperties ()) {
1055
+ var hlp = ((MappingSupport .RelationshipPropertiesWithEntityHolder ) relatedValueToStore );
1056
+ var hasProcessedRelationshipEntity = stateMachine .hasProcessedRelationshipEntity (parentPropertyAccessor .getBean (), hlp .getRelatedEntity (), relationshipContext .getRelationship ());
1057
+ if (hasProcessedRelationshipEntity ) {
1058
+ stateMachine .requireIdUpdate (sourceEntity , relationshipDescription , canUseElementId , fromId , relatedInternalId , relationshipContext , relatedValueToStore , idProperty );
1059
+ update = false ;
1060
+ } else {
1061
+ stateMachine .storeProcessRelationshipEntity (hlp , parentPropertyAccessor .getBean (), hlp .getRelatedEntity (), relationshipContext .getRelationship ());
1062
+ }
1063
+ }
1040
1064
List <Object > rows = new ArrayList <>();
1041
1065
rows .add (properties );
1042
1066
statementHolder = statementHolder .addProperty (Constants .NAME_OF_RELATIONSHIP_LIST_PARAM , rows );
1043
1067
// in case of no properties the bind will just return an empty map
1044
- return neo4jClient
1045
- .query (renderer .render (statementHolder .getStatement ()))
1046
- .bind (convertIdValues (sourceEntity .getRequiredIdProperty (), fromId )) //
1068
+ if (update ) {
1069
+ return neo4jClient
1070
+ .query (renderer .render (statementHolder .getStatement ()))
1071
+ .bind (convertIdValues (sourceEntity .getRequiredIdProperty (), fromId )) //
1047
1072
.to (Constants .FROM_ID_PARAMETER_NAME ) //
1048
- .bind (relatedInternalId ) //
1073
+ .bind (relatedInternalId ) //
1049
1074
.to (Constants .TO_ID_PARAMETER_NAME ) //
1050
- .bind (idValue ) //
1051
- .to (Constants .NAME_OF_KNOWN_RELATIONSHIP_PARAM ) //
1052
- .bindAll (statementHolder .getProperties ())
1053
- .fetchAs (Object .class )
1054
- .mappedBy ((t , r ) -> IdentitySupport .mapperForRelatedIdValues (idProperty ).apply (r ))
1055
- .one ()
1056
- .flatMap (relationshipInternalId -> {
1057
- if (idProperty != null && isNewRelationship ) {
1058
- relationshipContext
1059
- .getRelationshipPropertiesPropertyAccessor (relatedValueToStore )
1060
- .setProperty (idProperty , relationshipInternalId );
1061
- }
1062
-
1063
- Mono <Object > nestedRelationshipsSignal = null ;
1064
- if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
1065
- nestedRelationshipsSignal = processNestedRelations (targetEntity , targetPropertyAccessor , targetEntity .isNew (newRelatedObject ), stateMachine , includeProperty , currentPropertyPath );
1066
- }
1067
-
1068
- Mono <Object > getRelationshipOrRelationshipPropertiesObject = Mono .fromSupplier (() -> MappingSupport .getRelationshipOrRelationshipPropertiesObject (
1069
- neo4jMappingContext ,
1070
- relationshipDescription .hasRelationshipProperties (),
1071
- relationshipProperty .isDynamicAssociation (),
1072
- relatedValueToStore ,
1073
- targetPropertyAccessor ));
1074
- return nestedRelationshipsSignal == null ? getRelationshipOrRelationshipPropertiesObject :
1075
- nestedRelationshipsSignal .then (getRelationshipOrRelationshipPropertiesObject );
1076
- });
1075
+ .bind (idValue ) //
1076
+ .to (Constants .NAME_OF_KNOWN_RELATIONSHIP_PARAM ) //
1077
+ .bindAll (statementHolder .getProperties ())
1078
+ .fetchAs (Object .class )
1079
+ .mappedBy ((t , r ) -> IdentitySupport .mapperForRelatedIdValues (idProperty ).apply (r ))
1080
+ .one ()
1081
+ .flatMap (relationshipInternalId -> {
1082
+ if (idProperty != null && isNewRelationship ) {
1083
+ relationshipContext
1084
+ .getRelationshipPropertiesPropertyAccessor (relatedValueToStore )
1085
+ .setProperty (idProperty , relationshipInternalId );
1086
+ knownRelationshipsIds .add (relationshipInternalId );
1087
+ }
1088
+
1089
+ Mono <Object > nestedRelationshipsSignal = null ;
1090
+ if (processState != ProcessState .PROCESSED_ALL_VALUES ) {
1091
+ nestedRelationshipsSignal = processNestedRelations (targetEntity , targetPropertyAccessor , targetEntity .isNew (newRelatedObject ), stateMachine , knownRelationshipsIds , includeProperty , currentPropertyPath );
1092
+ }
1093
+
1094
+ Mono <Object > getRelationshipOrRelationshipPropertiesObject = Mono .fromSupplier (() -> MappingSupport .getRelationshipOrRelationshipPropertiesObject (
1095
+ neo4jMappingContext ,
1096
+ relationshipDescription .hasRelationshipProperties (),
1097
+ relationshipProperty .isDynamicAssociation (),
1098
+ relatedValueToStore ,
1099
+ targetPropertyAccessor ));
1100
+ return nestedRelationshipsSignal == null ? getRelationshipOrRelationshipPropertiesObject :
1101
+ nestedRelationshipsSignal .then (getRelationshipOrRelationshipPropertiesObject );
1102
+ });
1103
+ }
1104
+ return Mono .fromSupplier (() -> MappingSupport .getRelationshipOrRelationshipPropertiesObject (
1105
+ neo4jMappingContext ,
1106
+ relationshipDescription .hasRelationshipProperties (),
1107
+ relationshipProperty .isDynamicAssociation (),
1108
+ relatedValueToStore ,
1109
+ targetPropertyAccessor ));
1077
1110
})
1078
1111
.doOnNext (potentiallyRecreatedRelatedObject -> {
1079
1112
RelationshipHandler handler = ctx .get (CONTEXT_RELATIONSHIP_HANDLER );
@@ -1095,11 +1128,24 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
1095
1128
.thenMany (Flux .concat (relationshipCreationCreations ))
1096
1129
.doOnNext (objects -> objects .applyFinalResultToOwner (parentPropertyAccessor ))
1097
1130
.checkpoint ()
1131
+ .then (stateMachine .updateRelationshipIds (this ::getRelationshipId ))
1098
1132
.then (Mono .fromSupplier (parentPropertyAccessor ::getBean ));
1099
1133
return deleteAndThanCreateANew ;
1100
1134
1101
1135
}
1102
1136
1137
+ private Mono <Object > getRelationshipId (Statement statement , Neo4jPersistentProperty idProperty , Object fromId , Object toId ) {
1138
+
1139
+ return neo4jClient .query (renderer .render (statement ))
1140
+ .bind (convertIdValues (idProperty , fromId )) //
1141
+ .to (Constants .FROM_ID_PARAMETER_NAME ) //
1142
+ .bind (toId ) //
1143
+ .to (Constants .TO_ID_PARAMETER_NAME ) //
1144
+ .fetchAs (Object .class )
1145
+ .mappedBy ((t , r ) -> IdentitySupport .mapperForRelatedIdValues (idProperty ).apply (r ))
1146
+ .one ();
1147
+ }
1148
+
1103
1149
// The pendant to {@link #saveRelatedNode(Object, Neo4jPersistentEntity, PropertyFilter, PropertyFilter.RelaxedPropertyPath)}
1104
1150
// We can't do without a query, as we need to refresh the internal id
1105
1151
private Mono <Entity > loadRelatedNode (NodeDescription <?> targetNodeDescription , Object relatedInternalId ) {
0 commit comments