diff --git a/src/main/java/org/springframework/data/neo4j/core/DynamicLabels.java b/src/main/java/org/springframework/data/neo4j/core/DynamicLabels.java index 2be8dc1e10..6a8f3a1752 100644 --- a/src/main/java/org/springframework/data/neo4j/core/DynamicLabels.java +++ b/src/main/java/org/springframework/data/neo4j/core/DynamicLabels.java @@ -52,6 +52,12 @@ final class DynamicLabels implements UnaryOperator { public OngoingMatchAndUpdate apply(OngoingMatchAndUpdate ongoingMatchAndUpdate) { OngoingMatchAndUpdate decoratedMatchAndUpdate = ongoingMatchAndUpdate; + + if (oldLabels.equals(newLabels) || oldLabels.isEmpty()) { + // Returning if old label equals new label or old labels are empty, nothing to do here + return decoratedMatchAndUpdate; + } + if (!oldLabels.isEmpty()) { decoratedMatchAndUpdate = decoratedMatchAndUpdate.remove(rootNode, oldLabels.toArray(new String[0])); } @@ -60,4 +66,8 @@ public OngoingMatchAndUpdate apply(OngoingMatchAndUpdate ongoingMatchAndUpdate) } return decoratedMatchAndUpdate; } + + public List getNewLabels() { + return newLabels; + } } diff --git a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java index f25950666c..6cadce4e12 100644 --- a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java @@ -443,6 +443,10 @@ private T saveImpl(T instance, @Nullable Collection binderFunction = TemplateSupport.createAndApplyPropertyFilter( includedProperties, entityMetaData, diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java index 8e8bceca6b..bd0667849d 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java @@ -458,6 +458,10 @@ private Mono saveImpl(T instance, @Nullable Collection binderFunction = TemplateSupport.createAndApplyPropertyFilter( includedProperties, entityMetaData, diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java b/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java index 9eaccb6c47..66cf34ae8c 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java @@ -303,6 +303,10 @@ public Statement prepareSaveOf(NodeDescription nodeDescription, String primaryLabel = nodeDescription.getPrimaryLabel(); List additionalLabels = nodeDescription.getAdditionalLabels(); + List additionalLabelsNew = new ArrayList<>(additionalLabels); + additionalLabelsNew.addAll(nodeDescription.getDynamicLabels()); + Node rootNodeWithDynamicLabels = node(primaryLabel, additionalLabelsNew).named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); + Node rootNode = node(primaryLabel, additionalLabels).named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); IdDescription idDescription = nodeDescription.getIdDescription(); Assert.notNull(idDescription, "Cannot save individual nodes without an id attribute"); @@ -320,9 +324,9 @@ public Statement prepareSaveOf(NodeDescription nodeDescription, .where(createCompositePropertyCondition(idPropertyDescription, possibleExistingNode.getRequiredSymbolicName(), idParameter)) .with(possibleExistingNode) .where(possibleExistingNode.isNull()) - .create(rootNode.withProperties(versionProperty, literalOf(0))) - .with(rootNode) - .mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode) + .create(rootNodeWithDynamicLabels.withProperties(versionProperty, literalOf(0))) + .with(rootNodeWithDynamicLabels) + .mutate(rootNodeWithDynamicLabels, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNodeWithDynamicLabels) .build(); Statement updateIfExists = updateDecorator.apply(match(rootNode) @@ -345,9 +349,9 @@ public Statement prepareSaveOf(NodeDescription nodeDescription, .where(createCompositePropertyCondition(idPropertyDescription, possibleExistingNode.getRequiredSymbolicName(), idParameter)) .with(possibleExistingNode) .where(possibleExistingNode.isNull()) - .create(rootNode) - .with(rootNode) - .mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode) + .create(rootNodeWithDynamicLabels) + .with(rootNodeWithDynamicLabels) + .mutate(rootNodeWithDynamicLabels, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNodeWithDynamicLabels) .build(); Statement updateIfExists = updateDecorator.apply(match(rootNode) @@ -375,10 +379,10 @@ public Statement prepareSaveOf(NodeDescription nodeDescription, .where(nodeIdFunction.apply(possibleExistingNode).isEqualTo(idParameter)) .with(possibleExistingNode) .where(possibleExistingNode.isNull()) - .create(rootNode.withProperties(versionProperty, literalOf(0))) - .with(rootNode) - .mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))) - .returning(rootNode) + .create(rootNodeWithDynamicLabels.withProperties(versionProperty, literalOf(0))) + .with(rootNodeWithDynamicLabels) + .mutate(rootNodeWithDynamicLabels, parameter(Constants.NAME_OF_PROPERTIES_PARAM))) + .returning(rootNodeWithDynamicLabels) .build(); updateIfExists = updateDecorator.apply(match(rootNode) @@ -393,9 +397,9 @@ public Statement prepareSaveOf(NodeDescription nodeDescription, } else { createIfNew = updateDecorator .apply(optionalMatch(possibleExistingNode).where(nodeIdFunction.apply(possibleExistingNode).isEqualTo(idParameter)) - .with(possibleExistingNode).where(possibleExistingNode.isNull()).create(rootNode) - .set(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))) - .returning(rootNode).build(); + .with(possibleExistingNode).where(possibleExistingNode.isNull()).create(rootNodeWithDynamicLabels) + .set(rootNodeWithDynamicLabels, parameter(Constants.NAME_OF_PROPERTIES_PARAM))) + .returning(rootNodeWithDynamicLabels).build(); updateIfExists = updateDecorator.apply(match(rootNode).where(nodeIdFunction.apply(rootNode).isEqualTo(idParameter)) .mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode).build(); diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java index d5df9f90af..4e63ab3904 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java @@ -71,6 +71,8 @@ final class DefaultNeo4jPersistentEntity extends BasicPersistentEntity> additionalLabels; + private List dynamicLabels; + /** * Projections need to be also be eligible entities but don't define id fields. */ @@ -90,8 +92,8 @@ final class DefaultNeo4jPersistentEntity extends BasicPersistentEntity information) { super(information); - - this.primaryLabel = computePrimaryLabel(this.getType()); + this.dynamicLabels = new ArrayList<>(); + this.primaryLabel = computePrimaryLabel(this.getType()); this.additionalLabels = Lazy.of(this::computeAdditionalLabels); this.graphProperties = Lazy.of(this::computeGraphProperties); this.dynamicLabelsProperty = Lazy.of(() -> getGraphProperties().stream().map(Neo4jPersistentProperty.class::cast) @@ -166,6 +168,11 @@ public List getAdditionalLabels() { return this.additionalLabels.get(); } + @Override + public List getDynamicLabels() { + return this.dynamicLabels; + } + /* * (non-Javadoc) * @see NodeDescription#getGraphProperty(String) diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescription.java b/src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescription.java index 6ab63eae8b..c1bb3559a5 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescription.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescription.java @@ -48,6 +48,11 @@ public interface NodeDescription { */ List getAdditionalLabels(); + /** + * @return a list of dynamic label names applied to the entity + */ + List getDynamicLabels(); + /** * @return The list of all static labels, that is the union of {@link #getPrimaryLabel()} + * {@link #getAdditionalLabels()}. Order is guaranteed to be the primary first, then the others.