Skip to content

Commit c050fc3

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

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -939,17 +939,24 @@ private void assignIdToRelationshipProperties(NestedRelationshipContext relation
939939

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

942-
DynamicLabels dynamicLabels = determineDynamicLabels(entity, (Neo4jPersistentEntity<?>) targetNodeDescription);
942+
Neo4jPersistentEntity<?> targetPersistentEntity = (Neo4jPersistentEntity<?>) targetNodeDescription;
943+
DynamicLabels dynamicLabels = determineDynamicLabels(entity, targetPersistentEntity);
943944
@SuppressWarnings("rawtypes")
944-
Class entityType = ((Neo4jPersistentEntity<?>) targetNodeDescription).getType();
945+
Class entityType = targetPersistentEntity.getType();
945946
@SuppressWarnings("unchecked")
946947
Function<Object, Map<String, Object>> binderFunction = neo4jMappingContext.getRequiredBinderFunctionFor(entityType);
947948
binderFunction = binderFunction.andThen(tree -> {
948949
@SuppressWarnings("unchecked")
949950
Map<String, Object> properties = (Map<String, Object>) tree.get(Constants.NAME_OF_PROPERTIES_PARAM);
950-
951+
String idPropertyName = targetPersistentEntity.getIdProperty().getPropertyName();
952+
boolean assignedId = targetPersistentEntity.getIdDescription().isAssignedId();
951953
if (!includeProperty.isNotFiltering()) {
952-
properties.entrySet().removeIf(e -> !includeProperty.contains(currentPropertyPath.append(e.getKey())));
954+
properties.entrySet()
955+
.removeIf(e -> {
956+
// we cannot skip the id property if it is an assigned id
957+
boolean isIdProperty = e.getKey().equals(idPropertyName);
958+
return !(assignedId && isIdProperty) && !includeProperty.contains(currentPropertyPath.append(e.getKey()));
959+
});
953960
}
954961
return tree;
955962
});
@@ -959,7 +966,7 @@ private Entity saveRelatedNode(Object entity, NodeDescription<?> targetNodeDescr
959966
.fetchAs(Entity.class)
960967
.one();
961968

962-
if (((Neo4jPersistentEntity<?>) targetNodeDescription).hasVersionProperty() && !optionalSavedNode.isPresent()) {
969+
if (targetPersistentEntity.hasVersionProperty() && !optionalSavedNode.isPresent()) {
963970
throw new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE);
964971
}
965972

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,12 +1052,18 @@ private Mono<Entity> saveRelatedNode(Object relatedNode, Neo4jPersistentEntity<?
10521052
DynamicLabels dynamicLabels = t.getT2();
10531053
@SuppressWarnings("unchecked")
10541054
Function<Object, Map<String, Object>> binderFunction = neo4jMappingContext.getRequiredBinderFunctionFor(entityType);
1055+
String idPropertyName = targetNodeDescription.getIdProperty().getPropertyName();
1056+
boolean assignedId = targetNodeDescription.getIdDescription().isAssignedId();
10551057
binderFunction = binderFunction.andThen(tree -> {
10561058
@SuppressWarnings("unchecked")
10571059
Map<String, Object> properties = (Map<String, Object>) tree.get(Constants.NAME_OF_PROPERTIES_PARAM);
10581060

10591061
if (!includeProperty.isNotFiltering()) {
1060-
properties.entrySet().removeIf(e -> !includeProperty.contains(currentPropertyPath.append(e.getKey())));
1062+
properties.entrySet().removeIf(e -> {
1063+
// we cannot skip the id property if it is an assigned id
1064+
boolean isIdProperty = e.getKey().equals(idPropertyName);
1065+
return !(assignedId && isIdProperty) && !includeProperty.contains(currentPropertyPath.append(e.getKey()));
1066+
});
10611067
}
10621068
return tree;
10631069
});

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

Lines changed: 7 additions & 1 deletion
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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,28 @@ public Statement prepareSaveOf(NodeDescription<?> nodeDescription,
293293
return Cypher.union(createIfNew, updateIfExists);
294294

295295
} else {
296-
return updateDecorator.apply(Cypher.merge(rootNode.withProperties(nameOfIdProperty, idParameter)).mutate(rootNode,
297-
parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode).build();
296+
// if (1==1)
297+
// return updateDecorator.apply(Cypher.merge(rootNode.withProperties(nameOfIdProperty, idParameter)).mutate(rootNode,
298+
// parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode).build();
299+
String nameOfPossibleExistingNode = "hlp";
300+
Node possibleExistingNode = node(primaryLabel, additionalLabels).named(nameOfPossibleExistingNode);
301+
302+
Statement createIfNew = updateDecorator.apply(optionalMatch(possibleExistingNode)
303+
.where(possibleExistingNode.property(nameOfIdProperty).isEqualTo(idParameter))
304+
.with(possibleExistingNode)
305+
.where(possibleExistingNode.isNull())
306+
.create(rootNode)
307+
.with(rootNode)
308+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode)
309+
.build();
310+
311+
Statement updateIfExists = updateDecorator.apply(match(rootNode)
312+
.where(rootNode.property(nameOfIdProperty).isEqualTo(idParameter))
313+
.with(rootNode)
314+
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
315+
.returning(rootNode)
316+
.build();
317+
return Cypher.union(createIfNew, updateIfExists);
298318
}
299319
} else {
300320
String nameOfPossibleExistingNode = "hlp";

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ void setupData() {
110110

111111
transaction.run("CREATE (p:Person{firstName: 'A', lastName: 'LA'})");
112112
transaction.run("CREATE (p:Person{firstName: 'Michael', lastName: 'Siemons'})" +
113-
" -[:LIVES_AT]-> (a:Address {city: 'Aachen'})" +
113+
" -[:LIVES_AT]-> (a:Address {city: 'Aachen', id: 1})" +
114114
" -[:BASED_IN]->(c:YetAnotherCountryEntity{name: 'Gemany', countryCode: 'DE'})" +
115115
" RETURN id(p)");
116116
transaction.run(

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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)