Skip to content

Commit 56240cf

Browse files
GH-2259 - Explicitly acquire locks on nodes when @Version is used.
This change brings back the original locking behaviour from Neo4j-OGM: The version is incremented inside the database and with it, a physical lock on the Node is acquired as described in https://neo4j.com/docs/java-reference/current/transaction-management/ under "Explicitly acquire a write lock". That incremented version is than used in a where clause after pipelining the matched node. The changes makes it necessary to retrieve the changed property from the database and apply it to the domain entity afterwards. The callbacks used before became superflous. This commit brings the ability to fetch single Nodes, Relationships or in general (driver) entities via the Neo4j client, allowing us to fetch the changed structure without additional mapping functions. This fixes #2259.
1 parent b0693ec commit 56240cf

File tree

12 files changed

+185
-209
lines changed

12 files changed

+185
-209
lines changed

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

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.neo4j.driver.exceptions.NoSuchRecordException;
4545
import org.neo4j.driver.summary.ResultSummary;
4646
import org.neo4j.driver.summary.SummaryCounters;
47+
import org.neo4j.driver.types.Entity;
4748
import org.springframework.beans.BeansException;
4849
import org.springframework.beans.factory.BeanFactory;
4950
import org.springframework.beans.factory.BeanFactoryAware;
@@ -236,27 +237,28 @@ private <T> T saveImpl(T instance, @Nullable String inDatabase) {
236237

237238
DynamicLabels dynamicLabels = determineDynamicLabels(entityToBeSaved, entityMetaData, inDatabase);
238239

239-
Optional<Long> optionalInternalId = neo4jClient
240+
Optional<Entity> newOrUpdatedNode = neo4jClient
240241
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels)))
241242
.in(inDatabase)
242243
.bind(entityToBeSaved)
243244
.with(neo4jMappingContext.getRequiredBinderFunctionFor((Class<T>) entityToBeSaved.getClass()))
244-
.fetchAs(Long.class).one();
245+
.fetchAs(Entity.class).one();
245246

246247

247-
if (!optionalInternalId.isPresent()) {
248+
if (!newOrUpdatedNode.isPresent()) {
248249
if (entityMetaData.hasVersionProperty()) {
249250
throw new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE);
250251
}
251252
// defensive exception throwing
252253
throw new IllegalStateException("Could not retrieve an internal id while saving.");
253254
}
254255

255-
Long internalId = optionalInternalId.get();
256+
Long internalId = newOrUpdatedNode.get().id();
256257
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved);
257258
if (entityMetaData.isUsingInternalIds()) {
258259
propertyAccessor.setProperty(entityMetaData.getRequiredIdProperty(), internalId);
259260
}
261+
TemplateSupport.updateVersionPropertyIfPossible(entityMetaData, propertyAccessor, newOrUpdatedNode.get());
260262
processRelations(entityMetaData, instance, internalId, propertyAccessor, inDatabase, isEntityNew);
261263

262264
return propertyAccessor.getBean();
@@ -275,7 +277,7 @@ private <T> DynamicLabels determineDynamicLabels(T entityToBeSaved, Neo4jPersist
275277

276278
if (entityMetaData.hasVersionProperty()) {
277279
runnableQuery = runnableQuery
278-
.bind((Long) propertyAccessor.getProperty(entityMetaData.getRequiredVersionProperty()) - 1)
280+
.bind((Long) propertyAccessor.getProperty(entityMetaData.getRequiredVersionProperty()))
279281
.to(Constants.NAME_OF_VERSION_PARAM);
280282
}
281283

@@ -556,11 +558,13 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
556558
: eventSupport.maybeCallBeforeBind(relatedObjectBeforeCallbacksApplied);
557559

558560
Long relatedInternalId;
561+
Entity savedEntity = null;
559562
// No need to save values if processed
560563
if (stateMachine.hasProcessedValue(relatedValueToStore)) {
561564
relatedInternalId = stateMachine.getInternalId(relatedObjectBeforeCallbacksApplied);
562565
} else {
563-
relatedInternalId = saveRelatedNode(newRelatedObject, targetEntity, inDatabase);
566+
savedEntity = saveRelatedNode(newRelatedObject, targetEntity, inDatabase);
567+
relatedInternalId = savedEntity.id();
564568
}
565569
stateMachine.markValueAsProcessed(relatedValueToStore, relatedInternalId);
566570

@@ -595,6 +599,9 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
595599
if (targetEntity.isUsingInternalIds()) {
596600
targetPropertyAccessor.setProperty(targetEntity.getRequiredIdProperty(), relatedInternalId);
597601
}
602+
if (savedEntity != null) {
603+
TemplateSupport.updateVersionPropertyIfPossible(targetEntity, targetPropertyAccessor, savedEntity);
604+
}
598605
stateMachine.markValueAsProcessedAs(relatedObjectBeforeCallbacksApplied, targetPropertyAccessor.getBean());
599606

600607
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
@@ -616,21 +623,21 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
616623
return (T) propertyAccessor.getBean();
617624
}
618625

619-
private <Y> Long saveRelatedNode(Object entity, NodeDescription targetNodeDescription, @Nullable String inDatabase) {
626+
private <Y> Entity saveRelatedNode(Object entity, NodeDescription targetNodeDescription, @Nullable String inDatabase) {
620627

621628
DynamicLabels dynamicLabels = determineDynamicLabels(entity, (Neo4jPersistentEntity) targetNodeDescription,
622629
inDatabase);
623630
Class<Y> entityType = (Class<Y>) ((Neo4jPersistentEntity<?>) targetNodeDescription).getType();
624-
Optional<Long> optionalSavedNodeId = neo4jClient
631+
Optional<Entity> optionalSavedNode = neo4jClient
625632
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels)))
626633
.in(inDatabase).bind((Y) entity).with(neo4jMappingContext.getRequiredBinderFunctionFor(entityType))
627-
.fetchAs(Long.class).one();
634+
.fetchAs(Entity.class).one();
628635

629-
if (((Neo4jPersistentEntity) targetNodeDescription).hasVersionProperty() && !optionalSavedNodeId.isPresent()) {
636+
if (((Neo4jPersistentEntity) targetNodeDescription).hasVersionProperty() && !optionalSavedNode.isPresent()) {
630637
throw new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE);
631638
}
632639

633-
return optionalSavedNodeId.get();
640+
return optionalSavedNode.get();
634641
}
635642

636643
private String getDatabaseName() {

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

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.neo4j.cypherdsl.core.renderer.Renderer;
4747
import org.neo4j.driver.exceptions.NoSuchRecordException;
4848
import org.neo4j.driver.summary.SummaryCounters;
49+
import org.neo4j.driver.types.Entity;
4950
import org.reactivestreams.Publisher;
5051
import org.springframework.beans.BeansException;
5152
import org.springframework.beans.factory.BeanFactory;
@@ -240,9 +241,9 @@ private <T> Mono<T> saveImpl(T instance, @Nullable String inDatabase) {
240241

241242
Statement saveStatement = cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels);
242243

243-
Mono<Long> idMono = this.neo4jClient.query(() -> renderer.render(saveStatement)).in(inDatabase)
244+
Mono<Entity> idMono = this.neo4jClient.query(() -> renderer.render(saveStatement)).in(inDatabase)
244245
.bind(entityToBeSaved).with(neo4jMappingContext.getRequiredBinderFunctionFor((Class<T>) entityToBeSaved.getClass()))
245-
.fetchAs(Long.class).one()
246+
.fetchAs(Entity.class).one()
246247
.switchIfEmpty(Mono.defer(() -> {
247248
if (entityMetaData.hasVersionProperty()) {
248249
return Mono.error(() -> new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE));
@@ -251,11 +252,13 @@ private <T> Mono<T> saveImpl(T instance, @Nullable String inDatabase) {
251252
}));
252253

253254
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved);
254-
return idMono.doOnNext(internalId -> {
255+
return idMono.doOnNext(newOrUpdatedNode -> {
255256
if (entityMetaData.isUsingInternalIds()) {
256-
propertyAccessor.setProperty(entityMetaData.getRequiredIdProperty(), internalId);
257+
propertyAccessor.setProperty(entityMetaData.getRequiredIdProperty(), newOrUpdatedNode.id());
257258
}
258-
}).flatMap(internalId -> processRelations(entityMetaData, instance, internalId, propertyAccessor, inDatabase, isNewEntity));
259+
TemplateSupport.updateVersionPropertyIfPossible(entityMetaData, propertyAccessor, newOrUpdatedNode);
260+
}).map(Entity::id)
261+
.flatMap(internalId -> processRelations(entityMetaData, instance, internalId, propertyAccessor, inDatabase, isNewEntity));
259262
});
260263
}
261264

@@ -271,7 +274,7 @@ private <T> Mono<Tuple2<T, DynamicLabels>> determineDynamicLabels(T entityToBeSa
271274

272275
if (entityMetaData.hasVersionProperty()) {
273276
runnableQuery = runnableQuery
274-
.bind((Long) propertyAccessor.getProperty(entityMetaData.getRequiredVersionProperty()) - 1)
277+
.bind((Long) propertyAccessor.getProperty(entityMetaData.getRequiredVersionProperty()))
275278
.to(Constants.NAME_OF_VERSION_PARAM);
276279
}
277280

@@ -666,21 +669,31 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
666669
.flatMap(newRelatedObject -> {
667670
Neo4jPersistentEntity<?> targetEntity = neo4jMappingContext.getPersistentEntity(relatedObjectBeforeCallbacksApplied.getClass());
668671

669-
Mono<Long> queryOrSave;
672+
Mono<Tuple2<Long, Long>> queryOrSave;
673+
long noVersion = Long.MIN_VALUE;
670674
if (stateMachine.hasProcessedValue(relatedValueToStore)) {
671-
queryOrSave = Mono.just(stateMachine.getInternalId(relatedObjectBeforeCallbacksApplied));
675+
queryOrSave = Mono.just(stateMachine.getInternalId(relatedObjectBeforeCallbacksApplied))
676+
.map(id -> Tuples.of(id, noVersion));
672677
} else {
673-
queryOrSave = saveRelatedNode(newRelatedObject, targetEntity, inDatabase);
678+
queryOrSave = saveRelatedNode(newRelatedObject, targetEntity, inDatabase)
679+
.map(entity -> Tuples.of(entity.id(), targetEntity.hasVersionProperty() ?
680+
entity.get(targetEntity.getVersionProperty().getPropertyName())
681+
.asLong() :
682+
noVersion));
674683
}
675684

676-
return queryOrSave.flatMap(relatedInternalId -> {
685+
return queryOrSave.flatMap(idAndVersion -> {
686+
long relatedInternalId = idAndVersion.getT1();
677687
stateMachine.markValueAsProcessed(relatedValueToStore, relatedInternalId);
678688
// if an internal id is used this must be set to link this entity in the next iteration
679689
PersistentPropertyAccessor<?> targetPropertyAccessor = targetEntity.getPropertyAccessor(newRelatedObject);
680690
if (targetEntity.isUsingInternalIds()) {
681691
targetPropertyAccessor.setProperty(targetEntity.getRequiredIdProperty(), relatedInternalId);
682692
stateMachine.markValueAsProcessedAs(newRelatedObject, targetPropertyAccessor.getBean());
683693
}
694+
if (targetEntity.hasVersionProperty() && idAndVersion.getT2() != noVersion) {
695+
targetPropertyAccessor.setProperty(targetEntity.getVersionProperty(), idAndVersion.getT2());
696+
}
684697

685698
Object idValue = idProperty != null
686699
? relationshipContext
@@ -764,19 +777,19 @@ private <Y> Mono<Long> queryRelatedNode(Object entity, Neo4jPersistentEntity<?>
764777
.fetchAs(Long.class).one();
765778
}
766779

767-
private <Y> Mono<Long> saveRelatedNode(Object relatedNode,
780+
private <Y> Mono<Entity> saveRelatedNode(Object relatedNode,
768781
Neo4jPersistentEntity<?> targetNodeDescription, @Nullable String inDatabase) {
769782

770783
return determineDynamicLabels((Y) relatedNode, targetNodeDescription, inDatabase)
771784
.flatMap(t -> {
772785
Y entity = t.getT1();
773-
Class<Y> entityType = (Class<Y>) ((Neo4jPersistentEntity<?>) targetNodeDescription).getType();
786+
Class<Y> entityType = (Class<Y>) targetNodeDescription.getType();
774787
DynamicLabels dynamicLabels = t.getT2();
775788

776789
return neo4jClient
777790
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels)))
778791
.in(inDatabase).bind((Y) entity).with(neo4jMappingContext.getRequiredBinderFunctionFor(entityType))
779-
.fetchAs(Long.class).one();
792+
.fetchAs(Entity.class).one();
780793
}).switchIfEmpty(Mono.defer(() -> {
781794
if (targetNodeDescription.hasVersionProperty()) {
782795
return Mono.error(() -> new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE));

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import java.util.stream.StreamSupport;
2525

2626
import org.apiguardian.api.API;
27+
import org.neo4j.driver.types.Entity;
28+
import org.springframework.data.mapping.PersistentPropertyAccessor;
29+
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
2730
import org.springframework.lang.Nullable;
2831

2932
/**
@@ -82,6 +85,17 @@ static Class<?> findCommonElementType(Iterable<?> collection) {
8285
return candidate;
8386
}
8487

88+
static void updateVersionPropertyIfPossible(
89+
Neo4jPersistentEntity<?> entityMetaData,
90+
PersistentPropertyAccessor<?> propertyAccessor,
91+
Entity newOrUpdatedNode
92+
) {
93+
if (entityMetaData.hasVersionProperty()) {
94+
propertyAccessor.setProperty(
95+
entityMetaData.getVersionProperty(), newOrUpdatedNode.get(entityMetaData.getVersionProperty().getPropertyName()).asLong());
96+
}
97+
}
98+
8599
private TemplateSupport() {
86100
}
87101
}

src/main/java/org/springframework/data/neo4j/core/convert/AdditionalTypes.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
import org.neo4j.driver.Value;
4141
import org.neo4j.driver.Values;
4242
import org.neo4j.driver.exceptions.value.LossyCoercion;
43+
import org.neo4j.driver.types.Entity;
44+
import org.neo4j.driver.types.Node;
45+
import org.neo4j.driver.types.Relationship;
4346
import org.springframework.core.convert.TypeDescriptor;
4447
import org.springframework.core.convert.converter.ConditionalConverter;
4548
import org.springframework.core.convert.converter.ConverterRegistry;
@@ -98,6 +101,9 @@ final class AdditionalTypes {
98101
hlp.add(ConverterBuilder.reading(Value.class, URI.class, AdditionalTypes::asURI).andWriting(AdditionalTypes::value));
99102
hlp.add(ConverterBuilder.reading(Value.class, TimeZone.class, AdditionalTypes::asTimeZone).andWriting(AdditionalTypes::value));
100103
hlp.add(ConverterBuilder.reading(Value.class, ZoneId.class, AdditionalTypes::asZoneId).andWriting(AdditionalTypes::value));
104+
hlp.add(ConverterBuilder.reading(Value.class, Entity.class, Value::asEntity));
105+
hlp.add(ConverterBuilder.reading(Value.class, Node.class, Value::asNode));
106+
hlp.add(ConverterBuilder.reading(Value.class, Relationship.class, Value::asRelationship));
101107

102108
CONVERTERS = Collections.unmodifiableList(hlp);
103109
}

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

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.neo4j.cypherdsl.core.Node;
2626
import org.neo4j.cypherdsl.core.Parameter;
2727
import org.neo4j.cypherdsl.core.PatternElement;
28+
import org.neo4j.cypherdsl.core.Property;
2829
import org.neo4j.cypherdsl.core.Relationship;
2930
import org.neo4j.cypherdsl.core.RelationshipPattern;
3031
import org.neo4j.cypherdsl.core.SortItem;
@@ -49,10 +50,12 @@
4950

5051
import static org.neo4j.cypherdsl.core.Cypher.anyNode;
5152
import static org.neo4j.cypherdsl.core.Cypher.listBasedOn;
53+
import static org.neo4j.cypherdsl.core.Cypher.literalOf;
5254
import static org.neo4j.cypherdsl.core.Cypher.match;
5355
import static org.neo4j.cypherdsl.core.Cypher.node;
5456
import static org.neo4j.cypherdsl.core.Cypher.optionalMatch;
5557
import static org.neo4j.cypherdsl.core.Cypher.parameter;
58+
import static org.neo4j.cypherdsl.core.Functions.coalesce;
5659

5760
/**
5861
* A generator based on the schema defined by node and relationship descriptions. Most methods return renderable Cypher
@@ -223,7 +226,7 @@ public Statement createStatementReturningDynamicLabels(NodeDescription<?> nodeDe
223226

224227
PersistentProperty versionProperty = ((Neo4jPersistentEntity) nodeDescription).getRequiredVersionProperty();
225228
versionCondition = rootNode.property(versionProperty.getName())
226-
.isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM));
229+
.isEqualTo(coalesce(parameter(Constants.NAME_OF_VERSION_PARAM), literalOf(0)));
227230
} else {
228231
versionCondition = Conditions.noCondition();
229232
}
@@ -261,26 +264,34 @@ public Statement prepareSaveOf(NodeDescription<?> nodeDescription,
261264
.orElseThrow(() -> new MappingException("External id does not correspond to a graph property!"));
262265

263266
if (((Neo4jPersistentEntity) nodeDescription).hasVersionProperty()) {
264-
265-
PersistentProperty versionProperty = ((Neo4jPersistentEntity) nodeDescription).getRequiredVersionProperty();
267+
Property versionProperty = rootNode.property(((Neo4jPersistentEntity) nodeDescription).getRequiredVersionProperty().getName());
266268
String nameOfPossibleExistingNode = "hlp";
267269
Node possibleExistingNode = node(primaryLabel, additionalLabels).named(nameOfPossibleExistingNode);
268270

269271
Statement createIfNew = updateDecorator.apply(optionalMatch(possibleExistingNode)
270-
.where(possibleExistingNode.property(nameOfIdProperty).isEqualTo(idParameter)).with(possibleExistingNode)
271-
.where(possibleExistingNode.isNull()).create(rootNode)
272-
.set(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode.internalId()).build();
273-
274-
Statement updateIfExists = updateDecorator
275-
.apply(match(rootNode).where(rootNode.property(nameOfIdProperty).isEqualTo(idParameter))
276-
.and(rootNode.property(versionProperty.getName()).isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM)))
277-
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
278-
.returning(rootNode.internalId()).build();
272+
.where(possibleExistingNode.property(nameOfIdProperty).isEqualTo(idParameter))
273+
.with(possibleExistingNode)
274+
.where(possibleExistingNode.isNull())
275+
.create(rootNode.withProperties(versionProperty, literalOf(0)))
276+
.with(rootNode)
277+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode)
278+
.build();
279+
280+
Statement updateIfExists = updateDecorator.apply(match(rootNode)
281+
.where(rootNode.property(nameOfIdProperty).isEqualTo(idParameter))
282+
.and(versionProperty.isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM))) // Initial check
283+
.set(versionProperty.to(versionProperty.add(literalOf(1)))) // Acquire lock
284+
.with(rootNode)
285+
.where(versionProperty.isEqualTo(coalesce(parameter(Constants.NAME_OF_VERSION_PARAM), literalOf(0)).add(
286+
literalOf(1))))
287+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
288+
.returning(rootNode)
289+
.build();
279290
return Cypher.union(createIfNew, updateIfExists);
280291

281292
} else {
282293
return updateDecorator.apply(Cypher.merge(rootNode.withProperties(nameOfIdProperty, idParameter)).mutate(rootNode,
283-
parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode.internalId()).build();
294+
parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode).build();
284295
}
285296
} else {
286297
String nameOfPossibleExistingNode = "hlp";
@@ -290,27 +301,36 @@ public Statement prepareSaveOf(NodeDescription<?> nodeDescription,
290301
Statement updateIfExists;
291302

292303
if (((Neo4jPersistentEntity) nodeDescription).hasVersionProperty()) {
293-
294-
PersistentProperty versionProperty = ((Neo4jPersistentEntity) nodeDescription).getRequiredVersionProperty();
295-
296-
createIfNew = updateDecorator
297-
.apply(optionalMatch(possibleExistingNode).where(possibleExistingNode.internalId().isEqualTo(idParameter))
298-
.with(possibleExistingNode).where(possibleExistingNode.isNull()).create(rootNode)
299-
.set(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
300-
.returning(rootNode.internalId()).build();
301-
302-
updateIfExists = updateDecorator.apply(match(rootNode).where(rootNode.internalId().isEqualTo(idParameter))
303-
.and(rootNode.property(versionProperty.getName()).isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM)))
304-
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode.internalId()).build();
304+
Property versionProperty = rootNode.property(((Neo4jPersistentEntity) nodeDescription).getRequiredVersionProperty().getName());
305+
306+
createIfNew = updateDecorator.apply(optionalMatch(possibleExistingNode)
307+
.where(possibleExistingNode.internalId().isEqualTo(idParameter))
308+
.with(possibleExistingNode)
309+
.where(possibleExistingNode.isNull())
310+
.create(rootNode.withProperties(versionProperty, literalOf(0)))
311+
.with(rootNode)
312+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
313+
.returning(rootNode)
314+
.build();
315+
316+
updateIfExists = updateDecorator.apply(match(rootNode)
317+
.where(rootNode.internalId().isEqualTo(idParameter))
318+
.and(versionProperty.isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM))) // Initial check
319+
.set(versionProperty.to(versionProperty.add(literalOf(1)))) // Acquire lock
320+
.with(rootNode)
321+
.where(versionProperty.isEqualTo(coalesce(parameter(Constants.NAME_OF_VERSION_PARAM), literalOf(0)).add(
322+
literalOf(1))))
323+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
324+
.returning(rootNode).build();
305325
} else {
306326
createIfNew = updateDecorator
307327
.apply(optionalMatch(possibleExistingNode).where(possibleExistingNode.internalId().isEqualTo(idParameter))
308328
.with(possibleExistingNode).where(possibleExistingNode.isNull()).create(rootNode)
309329
.set(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
310-
.returning(rootNode.internalId()).build();
330+
.returning(rootNode).build();
311331

312332
updateIfExists = updateDecorator.apply(match(rootNode).where(rootNode.internalId().isEqualTo(idParameter))
313-
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode.internalId()).build();
333+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode).build();
314334
}
315335

316336
return Cypher.union(createIfNew, updateIfExists);

0 commit comments

Comments
 (0)