Skip to content

Commit ef63722

Browse files
GH-2295 - Ensure same order of operations in both Neo4jTemplate and ReactiveNeo4jTemplate.
- Use the same order of operations in both templates - Make sure to check with the same objects whether an entity has been seen - Brace for null values of related internal ids (check before overwriting an existing id and in case, check whether it has been seen through another object) This closes #2295.
1 parent e8c2161 commit ef63722

File tree

3 files changed

+52
-31
lines changed

3 files changed

+52
-31
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -721,12 +721,27 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
721721
Entity savedEntity = null;
722722
// No need to save values if processed
723723
if (stateMachine.hasProcessedValue(relatedValueToStore)) {
724-
relatedInternalId = stateMachine.getInternalId(relatedObjectBeforeCallbacksApplied);
724+
relatedInternalId = stateMachine.getInternalId(relatedValueToStore);
725725
} else {
726726
savedEntity = saveRelatedNode(newRelatedObject, targetEntity);
727727
relatedInternalId = savedEntity.id();
728+
stateMachine.markValueAsProcessed(relatedValueToStore, relatedInternalId);
728729
}
729-
stateMachine.markValueAsProcessed(relatedValueToStore, relatedInternalId);
730+
731+
PersistentPropertyAccessor<?> targetPropertyAccessor = targetEntity.getPropertyAccessor(newRelatedObject);
732+
// if an internal id is used this must be set to link this entity in the next iteration
733+
if (targetEntity.isUsingInternalIds()) {
734+
Neo4jPersistentProperty requiredIdProperty = targetEntity.getRequiredIdProperty();
735+
if (relatedInternalId == null && targetPropertyAccessor.getProperty(requiredIdProperty) != null) {
736+
relatedInternalId = (Long) targetPropertyAccessor.getProperty(requiredIdProperty);
737+
} else if (targetPropertyAccessor.getProperty(requiredIdProperty) == null) {
738+
targetPropertyAccessor.setProperty(requiredIdProperty, relatedInternalId);
739+
}
740+
}
741+
if (savedEntity != null) {
742+
TemplateSupport.updateVersionPropertyIfPossible(targetEntity, targetPropertyAccessor, savedEntity);
743+
}
744+
stateMachine.markValueAsProcessedAs(relatedObjectBeforeCallbacksApplied, targetPropertyAccessor.getBean());
730745

731746
Object idValue = idProperty != null
732747
? relationshipContext
@@ -754,16 +769,6 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
754769
.setProperty(idProperty, relationshipInternalId.get());
755770
}
756771

757-
PersistentPropertyAccessor<?> targetPropertyAccessor = targetEntity.getPropertyAccessor(newRelatedObject);
758-
// if an internal id is used this must be set to link this entity in the next iteration
759-
if (targetEntity.isUsingInternalIds()) {
760-
targetPropertyAccessor.setProperty(targetEntity.getRequiredIdProperty(), relatedInternalId);
761-
}
762-
if (savedEntity != null) {
763-
TemplateSupport.updateVersionPropertyIfPossible(targetEntity, targetPropertyAccessor, savedEntity);
764-
}
765-
stateMachine.markValueAsProcessedAs(relatedObjectBeforeCallbacksApplied, targetPropertyAccessor.getBean());
766-
767772
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
768773
processNestedRelations(targetEntity, targetPropertyAccessor, isEntityNew, stateMachine, s -> true);
769774
}

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -793,35 +793,48 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
793793
Flux<RelationshipHandler> relationshipCreation = Flux.fromIterable(relatedValuesToStore).concatMap(relatedValueToStore -> {
794794

795795
Object relatedObjectBeforeCallbacksApplied = relationshipContext.identifyAndExtractRelationshipTargetNode(relatedValueToStore);
796-
return Mono.deferContextual(ctx -> eventSupport
797-
.maybeCallBeforeBind(relatedObjectBeforeCallbacksApplied)
796+
return Mono.deferContextual(ctx ->
797+
798+
(stateMachine.hasProcessedValue(relatedObjectBeforeCallbacksApplied)
799+
? Mono.just(stateMachine.getProcessedAs(relatedObjectBeforeCallbacksApplied))
800+
: eventSupport.maybeCallBeforeBind(relatedObjectBeforeCallbacksApplied))
801+
798802
.flatMap(newRelatedObject -> {
799803
Neo4jPersistentEntity<?> targetEntity = neo4jMappingContext.getPersistentEntity(relatedObjectBeforeCallbacksApplied.getClass());
800804

801-
Mono<Tuple2<Long, Long>> queryOrSave;
802-
long noVersion = Long.MIN_VALUE;
805+
Mono<Tuple2<Long[], Long[]>> queryOrSave;
803806
if (stateMachine.hasProcessedValue(relatedValueToStore)) {
804-
queryOrSave = Mono.just(stateMachine.getInternalId(relatedObjectBeforeCallbacksApplied))
805-
.map(id -> Tuples.of(id, noVersion));
807+
queryOrSave = Mono.just(new Long[] {stateMachine.getInternalId(relatedValueToStore)})
808+
.map(id -> Tuples.of(id, new Long[1]));
806809
} else {
807810
queryOrSave = saveRelatedNode(newRelatedObject, targetEntity)
808-
.map(entity -> Tuples.of(entity.id(), targetEntity.hasVersionProperty() ?
809-
entity.get(targetEntity.getVersionProperty().getPropertyName())
810-
.asLong() :
811-
noVersion));
811+
.doOnNext(entity -> stateMachine.markValueAsProcessed(relatedValueToStore, entity.id()))
812+
.map(entity -> {
813+
Long version = targetEntity.hasVersionProperty() ?
814+
entity.get(targetEntity.getVersionProperty().getPropertyName()).asLong() :
815+
null;
816+
return Tuples.of(
817+
new Long[] { entity.id() },
818+
new Long[] { version });
819+
});
812820
}
813821
return queryOrSave.flatMap(idAndVersion -> {
814-
long relatedInternalId = idAndVersion.getT1();
815-
stateMachine.markValueAsProcessed(relatedValueToStore, relatedInternalId);
822+
Long relatedInternalId = idAndVersion.getT1()[0];
816823
// if an internal id is used this must be set to link this entity in the next iteration
817824
PersistentPropertyAccessor<?> targetPropertyAccessor = targetEntity.getPropertyAccessor(newRelatedObject);
818825
if (targetEntity.isUsingInternalIds()) {
819-
targetPropertyAccessor.setProperty(targetEntity.getRequiredIdProperty(), relatedInternalId);
820-
stateMachine.markValueAsProcessedAs(newRelatedObject, targetPropertyAccessor.getBean());
826+
Neo4jPersistentProperty requiredIdProperty = targetEntity.getRequiredIdProperty();
827+
if (relatedInternalId == null
828+
&& targetPropertyAccessor.getProperty(requiredIdProperty) != null) {
829+
relatedInternalId = (Long) targetPropertyAccessor.getProperty(requiredIdProperty);
830+
} else if (targetPropertyAccessor.getProperty(requiredIdProperty) == null) {
831+
targetPropertyAccessor.setProperty(requiredIdProperty, relatedInternalId);
832+
}
821833
}
822-
if (targetEntity.hasVersionProperty() && idAndVersion.getT2() != noVersion) {
823-
targetPropertyAccessor.setProperty(targetEntity.getVersionProperty(), idAndVersion.getT2());
834+
if (targetEntity.hasVersionProperty() && idAndVersion.getT2()[0] != null) {
835+
targetPropertyAccessor.setProperty(targetEntity.getVersionProperty(), idAndVersion.getT2()[0]);
824836
}
837+
stateMachine.markValueAsProcessedAs(relatedObjectBeforeCallbacksApplied, targetPropertyAccessor.getBean());
825838

826839
Object idValue = idProperty != null
827840
? relationshipContext
@@ -898,7 +911,7 @@ private <Y> Mono<Entity> saveRelatedNode(Object relatedNode, Neo4jPersistentEnti
898911

899912
return neo4jClient
900913
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels)))
901-
.bind((Y) entity).with(neo4jMappingContext.getRequiredBinderFunctionFor(entityType))
914+
.bind(entity).with(neo4jMappingContext.getRequiredBinderFunctionFor(entityType))
902915
.fetchAs(Entity.class)
903916
.one();
904917
}).switchIfEmpty(Mono.defer(() -> {

src/main/java/org/springframework/data/neo4j/core/mapping/NestedRelationshipProcessingStateMachine.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ public void markValueAsProcessed(Object valueToStore, Long internalId) {
162162
try {
163163
write.lock();
164164
this.processedObjects.add(valueToStore);
165-
this.processedObjectsIds.put(valueToStore, internalId);
165+
if (internalId != null) {
166+
this.processedObjectsIds.put(valueToStore, internalId);
167+
}
166168
} finally {
167169
write.unlock();
168170
}
@@ -175,7 +177,7 @@ public void markValueAsProcessed(Object valueToStore, Long internalId) {
175177
* @return processed yes (true) / no (false)
176178
*/
177179
public boolean hasProcessedValue(Object value) {
178-
return processedObjects.contains(value);
180+
return processedObjects.contains(value) || processedObjectsAlias.containsKey(value);
179181
}
180182

181183
/**
@@ -200,6 +202,7 @@ public void markValueAsProcessedAs(Object relatedValueToStore, Object bean) {
200202
}
201203
}
202204

205+
@Nullable
203206
public Long getInternalId(Object object) {
204207
try {
205208
read.lock();

0 commit comments

Comments
 (0)