Skip to content

Commit 1af7355

Browse files
committed
GH-2588 - Replace node creation's MERGE with UNION.
Ensure that no duplicates can get created. Closes #2588
1 parent 82c92b3 commit 1af7355

File tree

7 files changed

+51
-12
lines changed

7 files changed

+51
-12
lines changed

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

+12-5
Original file line numberDiff line numberDiff line change
@@ -968,17 +968,24 @@ private void assignIdToRelationshipProperties(NestedRelationshipContext relation
968968

969969
private Entity saveRelatedNode(Object entity, NodeDescription<?> targetNodeDescription, PropertyFilter includeProperty, PropertyFilter.RelaxedPropertyPath currentPropertyPath) {
970970

971-
DynamicLabels dynamicLabels = determineDynamicLabels(entity, (Neo4jPersistentEntity<?>) targetNodeDescription);
971+
Neo4jPersistentEntity<?> targetPersistentEntity = (Neo4jPersistentEntity<?>) targetNodeDescription;
972+
DynamicLabels dynamicLabels = determineDynamicLabels(entity, targetPersistentEntity);
972973
@SuppressWarnings("rawtypes")
973-
Class entityType = ((Neo4jPersistentEntity<?>) targetNodeDescription).getType();
974+
Class entityType = targetPersistentEntity.getType();
974975
@SuppressWarnings("unchecked")
975976
Function<Object, Map<String, Object>> binderFunction = neo4jMappingContext.getRequiredBinderFunctionFor(entityType);
976977
binderFunction = binderFunction.andThen(tree -> {
977978
@SuppressWarnings("unchecked")
978979
Map<String, Object> properties = (Map<String, Object>) tree.get(Constants.NAME_OF_PROPERTIES_PARAM);
979-
980+
String idPropertyName = targetPersistentEntity.getIdProperty().getPropertyName();
981+
boolean assignedId = targetPersistentEntity.getIdDescription().isAssignedId();
980982
if (!includeProperty.isNotFiltering()) {
981-
properties.entrySet().removeIf(e -> !includeProperty.contains(currentPropertyPath.append(e.getKey())));
983+
properties.entrySet()
984+
.removeIf(e -> {
985+
// we cannot skip the id property if it is an assigned id
986+
boolean isIdProperty = e.getKey().equals(idPropertyName);
987+
return !(assignedId && isIdProperty) && !includeProperty.contains(currentPropertyPath.append(e.getKey()));
988+
});
982989
}
983990
return tree;
984991
});
@@ -988,7 +995,7 @@ private Entity saveRelatedNode(Object entity, NodeDescription<?> targetNodeDescr
988995
.fetchAs(Entity.class)
989996
.one();
990997

991-
if (((Neo4jPersistentEntity<?>) targetNodeDescription).hasVersionProperty() && !optionalSavedNode.isPresent()) {
998+
if (targetPersistentEntity.hasVersionProperty() && !optionalSavedNode.isPresent()) {
992999
throw new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE);
9931000
}
9941001

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -1067,12 +1067,18 @@ private Mono<Entity> saveRelatedNode(Object relatedNode, Neo4jPersistentEntity<?
10671067
DynamicLabels dynamicLabels = t.getT2();
10681068
@SuppressWarnings("unchecked")
10691069
Function<Object, Map<String, Object>> binderFunction = neo4jMappingContext.getRequiredBinderFunctionFor(entityType);
1070+
String idPropertyName = targetNodeDescription.getIdProperty().getPropertyName();
1071+
boolean assignedId = targetNodeDescription.getIdDescription().isAssignedId();
10701072
binderFunction = binderFunction.andThen(tree -> {
10711073
@SuppressWarnings("unchecked")
10721074
Map<String, Object> properties = (Map<String, Object>) tree.get(Constants.NAME_OF_PROPERTIES_PARAM);
10731075

10741076
if (!includeProperty.isNotFiltering()) {
1075-
properties.entrySet().removeIf(e -> !includeProperty.contains(currentPropertyPath.append(e.getKey())));
1077+
properties.entrySet().removeIf(e -> {
1078+
// we cannot skip the id property if it is an assigned id
1079+
boolean isIdProperty = e.getKey().equals(idPropertyName);
1080+
return !(assignedId && isIdProperty) && !includeProperty.contains(currentPropertyPath.append(e.getKey()));
1081+
});
10761082
}
10771083
return tree;
10781084
});

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,15 @@ static <T> FilteredBinderFunction<T> createAndApplyPropertyFilter(
279279
@SuppressWarnings("unchecked")
280280
Map<String, Object> properties = (Map<String, Object>) tree.get(Constants.NAME_OF_PROPERTIES_PARAM);
281281

282+
String idPropertyName = entityMetaData.getIdProperty().getPropertyName();
283+
boolean assignedId = entityMetaData.getIdDescription().isAssignedId();
282284
if (!includeProperty.isNotFiltering()) {
283285
properties.entrySet()
284-
.removeIf(e -> !includeProperty.contains(e.getKey(), entityMetaData.getUnderlyingClass()));
286+
.removeIf(e -> {
287+
// we cannot skip the id property if it is an assigned id
288+
boolean isIdProperty = e.getKey().equals(idPropertyName);
289+
return !(assignedId && isIdProperty) && !includeProperty.contains(e.getKey(), entityMetaData.getUnderlyingClass());
290+
});
285291
}
286292
return tree;
287293
}));

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

+22-2
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,28 @@ public Statement prepareSaveOf(NodeDescription<?> nodeDescription,
301301
return Cypher.union(createIfNew, updateIfExists);
302302

303303
} else {
304-
return updateDecorator.apply(Cypher.merge(rootNode.withProperties(nameOfIdProperty, idParameter)).mutate(rootNode,
305-
parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode).build();
304+
// if (1==1)
305+
// return updateDecorator.apply(Cypher.merge(rootNode.withProperties(nameOfIdProperty, idParameter)).mutate(rootNode,
306+
// parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode).build();
307+
String nameOfPossibleExistingNode = "hlp";
308+
Node possibleExistingNode = node(primaryLabel, additionalLabels).named(nameOfPossibleExistingNode);
309+
310+
Statement createIfNew = updateDecorator.apply(optionalMatch(possibleExistingNode)
311+
.where(possibleExistingNode.property(nameOfIdProperty).isEqualTo(idParameter))
312+
.with(possibleExistingNode)
313+
.where(possibleExistingNode.isNull())
314+
.create(rootNode)
315+
.with(rootNode)
316+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode)
317+
.build();
318+
319+
Statement updateIfExists = updateDecorator.apply(match(rootNode)
320+
.where(rootNode.property(nameOfIdProperty).isEqualTo(idParameter))
321+
.with(rootNode)
322+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
323+
.returning(rootNode)
324+
.build();
325+
return Cypher.union(createIfNew, updateIfExists);
306326
}
307327
} else {
308328
String nameOfPossibleExistingNode = "hlp";

src/test/java/org/springframework/data/neo4j/integration/imperative/Neo4jTemplateIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ void setupData() {
117117

118118
transaction.run("CREATE (p:Person{firstName: 'A', lastName: 'LA'})");
119119
transaction.run("CREATE (p:Person{firstName: 'Michael', lastName: 'Siemons'})" +
120-
" -[:LIVES_AT]-> (a:Address {city: 'Aachen'})" +
120+
" -[:LIVES_AT]-> (a:Address {city: 'Aachen', id: 1})" +
121121
" -[:BASED_IN]->(c:YetAnotherCountryEntity{name: 'Gemany', countryCode: 'DE'})" +
122122
" RETURN id(p)");
123123
transaction.run(

src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveNeo4jTemplateIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ void setupData(@Autowired BookmarkCapture bookmarkCapture) {
117117
transaction.run("CREATE (p:Person{firstName: 'A', lastName: 'LA'})");
118118
simonsId = transaction
119119
.run("CREATE (p:Person{firstName: 'Michael', lastName: 'Siemons'})" +
120-
"-[:LIVES_AT]->(a:Address {city: 'Aachen'})" +
120+
"-[:LIVES_AT]->(a:Address {city: 'Aachen', id: 1})" +
121121
"-[:BASED_IN]->(c:YetAnotherCountryEntity{name: 'Gemany', countryCode: 'DE'})" +
122122
"RETURN id(p)")
123123
.single().get(0).asLong();

src/test/java/org/springframework/data/neo4j/integration/shared/common/Person.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class Person {
3939
*/
4040
@Node
4141
public static class Address {
42-
@Id @GeneratedValue private Long id;
42+
@Id private Long id;
4343
private String zipCode;
4444
private String city;
4545
private String street;

0 commit comments

Comments
 (0)