Skip to content

Commit 82c92b3

Browse files
committed
GH-2593 - Move relationship updates to batch operations.
This change explicitly excludes: - All reactive operations - Dynamic relationships Closes #2593
1 parent 0f51a3a commit 82c92b3

File tree

9 files changed

+269
-47
lines changed

9 files changed

+269
-47
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ void add(String name, @Nullable Object value) {
7070

7171
if (Constants.NAME_OF_PROPERTIES_PARAM.equals(name) && value != null) {
7272
this.parameters.put(name, unwrapMapValueWrapper((Map<String, Object>) value));
73+
} else if (Constants.NAME_OF_RELATIONSHIP_LIST_PARAM.equals(name) && value != null) {
74+
this.parameters.put(name, unwrapMapValueWrapperInListOfEntities((List<Map<String, Object>>) value));
7375
} else if (Constants.NAME_OF_ENTITY_LIST_PARAM.equals(name) && value != null) {
7476
this.parameters.put(name, unwrapMapValueWrapperInListOfEntities((List<Map<String, Object>>) value));
7577
} else {

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

Lines changed: 93 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,11 @@ private <T> T processNestedRelations(
799799
Neo4jPersistentProperty relationshipProperty = association.getInverse();
800800

801801
RelationshipHandler relationshipHandler = RelationshipHandler.forProperty(relationshipProperty, rawValue);
802+
List<Object> plainRelationshipRows = new ArrayList<>();
803+
List<Map<String, Object>> relationshipPropertiesRows = new ArrayList<>();
804+
List<Map<String, Object>> newRelationshipPropertiesRows = new ArrayList<>();
805+
List<Object> updateRelatedValuesToStore = new ArrayList<>();
806+
List<Object> newRelatedValuesToStore = new ArrayList<>();
802807

803808
for (Object relatedValueToStore : relatedValuesToStore) {
804809

@@ -847,28 +852,60 @@ private <T> T processNestedRelations(
847852

848853
Object idValue = idProperty != null
849854
? relationshipContext
850-
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore).getProperty(idProperty)
855+
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore).getProperty(idProperty)
851856
: null;
852857

858+
Map<String, Object> properties = new HashMap<>();
859+
properties.put(Constants.FROM_ID_PARAMETER_NAME, convertIdValues(sourceEntity.getRequiredIdProperty(), fromId));
860+
properties.put(Constants.TO_ID_PARAMETER_NAME, relatedInternalId);
861+
properties.put(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM, idValue);
853862
boolean isNewRelationship = idValue == null;
854-
855-
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatement(
856-
sourceEntity, relationshipContext, relatedValueToStore, isNewRelationship);
857-
858-
Optional<Long> relationshipInternalId = neo4jClient.query(renderer.render(statementHolder.getStatement()))
859-
.bind(convertIdValues(sourceEntity.getRequiredIdProperty(), fromId)) //
860-
.to(Constants.FROM_ID_PARAMETER_NAME) //
861-
.bind(relatedInternalId) //
862-
.to(Constants.TO_ID_PARAMETER_NAME) //
863-
.bind(idValue) //
864-
.to(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM) //
865-
.bindAll(statementHolder.getProperties())
866-
.fetchAs(Long.class).one();
867-
868-
if (idProperty != null && isNewRelationship) {
869-
relationshipContext
870-
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore)
871-
.setProperty(idProperty, relationshipInternalId.get());
863+
if (relationshipDescription.isDynamic()) {
864+
// create new dynamic relationship properties
865+
if (relationshipDescription.hasRelationshipProperties() && isNewRelationship && idProperty != null) {
866+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
867+
sourceEntity, relationshipDescription, relatedValueToStore, true);
868+
869+
List<Object> row = Collections.singletonList(properties);
870+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, row);
871+
Optional<Long> relationshipInternalId = neo4jClient.query(renderer.render(statementHolder.getStatement()))
872+
.bind(convertIdValues(sourceEntity.getRequiredIdProperty(), fromId)) //
873+
.to(Constants.FROM_ID_PARAMETER_NAME) //
874+
.bind(relatedInternalId) //
875+
.to(Constants.TO_ID_PARAMETER_NAME) //
876+
.bind(idValue) // always null
877+
.to(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM) //
878+
.bindAll(statementHolder.getProperties())
879+
.fetchAs(Long.class).one();
880+
assignIdToRelationshipProperties(relationshipContext, relatedValueToStore, idProperty, relationshipInternalId.get());
881+
} else { // plain (new or to update) dynamic relationship or dynamic relationships with properties to update
882+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
883+
sourceEntity, relationshipDescription, relatedValueToStore, false);
884+
885+
List<Object> row = Collections.singletonList(properties);
886+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, row);
887+
neo4jClient.query(renderer.render(statementHolder.getStatement()))
888+
.bind(convertIdValues(sourceEntity.getRequiredIdProperty(), fromId)) //
889+
.to(Constants.FROM_ID_PARAMETER_NAME) //
890+
.bind(relatedInternalId) //
891+
.to(Constants.TO_ID_PARAMETER_NAME) //
892+
.bind(idValue)
893+
.to(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM) //
894+
.bindAll(statementHolder.getProperties())
895+
.run();
896+
}
897+
} else if (relationshipDescription.hasRelationshipProperties() && isNewRelationship && idProperty != null) {
898+
newRelationshipPropertiesRows.add(properties);
899+
newRelatedValuesToStore.add(relatedValueToStore);
900+
} else if (relationshipDescription.hasRelationshipProperties()) {
901+
neo4jMappingContext.getEntityConverter().write(
902+
((MappingSupport.RelationshipPropertiesWithEntityHolder) relatedValueToStore).getRelationshipProperties(),
903+
properties);
904+
905+
relationshipPropertiesRows.add(properties);
906+
} else {
907+
// non-dynamic relationship or relationship with properties
908+
plainRelationshipRows.add(properties);
872909
}
873910

874911
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
@@ -883,6 +920,37 @@ private <T> T processNestedRelations(
883920

884921
relationshipHandler.handle(relatedValueToStore, relatedObjectBeforeCallbacksApplied, potentiallyRecreatedNewRelatedObject);
885922
}
923+
// batch operations
924+
if (!(relationshipDescription.hasRelationshipProperties() || relationshipDescription.isDynamic() || plainRelationshipRows.isEmpty())) {
925+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeSimpleRelationshipBatch(
926+
sourceEntity, relationshipDescription, plainRelationshipRows);
927+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, plainRelationshipRows);
928+
neo4jClient.query(renderer.render(statementHolder.getStatement()))
929+
.bindAll(statementHolder.getProperties())
930+
.run();
931+
} else if (relationshipDescription.hasRelationshipProperties()) {
932+
if (!relationshipPropertiesRows.isEmpty()) {
933+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeRelationshipsWithPropertiesBatch(false,
934+
sourceEntity, relationshipDescription, updateRelatedValuesToStore, relationshipPropertiesRows);
935+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, relationshipPropertiesRows);
936+
937+
neo4jClient.query(renderer.render(statementHolder.getStatement()))
938+
.bindAll(statementHolder.getProperties())
939+
.run();
940+
} else if (!newRelatedValuesToStore.isEmpty()) {
941+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeRelationshipsWithPropertiesBatch(true,
942+
sourceEntity, relationshipDescription, newRelatedValuesToStore, newRelationshipPropertiesRows);
943+
944+
List<Long> all = new ArrayList<>(neo4jClient.query(renderer.render(statementHolder.getStatement()))
945+
.bindAll(statementHolder.getProperties())
946+
.fetchAs(Long.class).all());
947+
// assign new ids
948+
for (int i = 0; i < all.size(); i++) {
949+
Long aLong = all.get(i);
950+
assignIdToRelationshipProperties(relationshipContext, newRelatedValuesToStore.get(i), idProperty, aLong);
951+
}
952+
}
953+
}
886954

887955
relationshipHandler.applyFinalResultToOwner(propertyAccessor);
888956
});
@@ -892,6 +960,12 @@ private <T> T processNestedRelations(
892960
return finalSubgraphRoot;
893961
}
894962

963+
private void assignIdToRelationshipProperties(NestedRelationshipContext relationshipContext, Object relatedValueToStore, Neo4jPersistentProperty idProperty, Long relationshipInternalId) {
964+
relationshipContext
965+
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore)
966+
.setProperty(idProperty, relationshipInternalId);
967+
}
968+
895969
private Entity saveRelatedNode(Object entity, NodeDescription<?> targetNodeDescription, PropertyFilter includeProperty, PropertyFilter.RelaxedPropertyPath currentPropertyPath) {
896970

897971
DynamicLabels dynamicLabels = determineDynamicLabels(entity, (Neo4jPersistentEntity<?>) targetNodeDescription);

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -989,9 +989,16 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
989989
: null;
990990

991991
boolean isNewRelationship = idValue == null;
992-
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatement(
993-
sourceEntity, relationshipContext, relatedValueToStore, isNewRelationship);
994-
992+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
993+
sourceEntity, relationshipDescription, relatedValueToStore, isNewRelationship);
994+
995+
Map<String, Object> properties = new HashMap<>();
996+
properties.put(Constants.FROM_ID_PARAMETER_NAME, convertIdValues(sourceEntity.getRequiredIdProperty(), fromId));
997+
properties.put(Constants.TO_ID_PARAMETER_NAME, relatedInternalId);
998+
properties.put(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM, idValue);
999+
List<Object> rows = new ArrayList<>();
1000+
rows.add(properties);
1001+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, rows);
9951002
// in case of no properties the bind will just return an empty map
9961003
return neo4jClient
9971004
.query(renderer.render(statementHolder.getStatement()))

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public final class Constants {
6060
*/
6161
public static final String NAME_OF_STATIC_LABELS_PARAM = "__staticLabels__";
6262
public static final String NAME_OF_ENTITY_LIST_PARAM = "__entities__";
63+
public static final String NAME_OF_RELATIONSHIP_LIST_PARAM = "__relationships__";
6364
public static final String NAME_OF_KNOWN_RELATIONSHIP_PARAM = "__knownRelationShipId__";
6465
public static final String NAME_OF_KNOWN_RELATIONSHIPS_PARAM = "__knownRelationShipIds__";
6566
public static final String NAME_OF_ALL_PROPERTIES = "__allProperties__";

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.data.neo4j.core.mapping;
1717

18-
import java.util.Collections;
18+
import java.util.HashMap;
1919
import java.util.Map;
2020

2121
import org.apiguardian.api.API;
@@ -40,10 +40,6 @@ public final class CreateRelationshipStatementHolder {
4040
private final Statement statement;
4141
private final Map<String, Object> properties;
4242

43-
CreateRelationshipStatementHolder(@NonNull Statement statement) {
44-
this(statement, Collections.emptyMap());
45-
}
46-
4743
CreateRelationshipStatementHolder(@NonNull Statement statement, @NonNull Map<String, Object> properties) {
4844
this.statement = statement;
4945
this.properties = properties;
@@ -56,4 +52,10 @@ public Statement getStatement() {
5652
public Map<String, Object> getProperties() {
5753
return properties;
5854
}
55+
56+
public CreateRelationshipStatementHolder addProperty(String key, Object property) {
57+
Map<String, Object> newProperties = new HashMap<>(this.properties);
58+
newProperties.put(key, property);
59+
return new CreateRelationshipStatementHolder(this.statement, newProperties);
60+
}
5961
}

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,36 @@ public Statement prepareSaveOfRelationship(Neo4jPersistentEntity<?> neo4jPersist
394394
.returning(Functions.id(relationshipFragment))
395395
.build();
396396
}
397+
@NonNull
398+
public Statement prepareSaveOfRelationships(Neo4jPersistentEntity<?> neo4jPersistentEntity,
399+
RelationshipDescription relationship, @Nullable String dynamicRelationshipType) {
400+
401+
final Node startNode = neo4jPersistentEntity.isUsingInternalIds()
402+
? anyNode(START_NODE_NAME)
403+
: node(neo4jPersistentEntity.getPrimaryLabel(), neo4jPersistentEntity.getAdditionalLabels())
404+
.named(START_NODE_NAME);
405+
406+
final Node endNode = anyNode(END_NODE_NAME);
407+
String idPropertyName = neo4jPersistentEntity.getRequiredIdProperty().getPropertyName();
408+
409+
String type = relationship.isDynamic() ? dynamicRelationshipType : relationship.getType();
410+
Relationship relationshipFragment = (relationship.isOutgoing() ?
411+
startNode.relationshipTo(endNode, type) :
412+
startNode.relationshipFrom(endNode, type)).named(RELATIONSHIP_NAME);
413+
414+
String row = "relationship";
415+
Property idProperty = Cypher.property(row, Constants.FROM_ID_PARAMETER_NAME);
416+
return Cypher.unwind(parameter(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM)).as(row)
417+
.with(row)
418+
.match(startNode)
419+
.where(neo4jPersistentEntity.isUsingInternalIds()
420+
? startNode.internalId().isEqualTo(idProperty)
421+
: startNode.property(idPropertyName).isEqualTo(idProperty))
422+
.match(endNode).where(endNode.internalId().isEqualTo(Cypher.property(row, Constants.TO_ID_PARAMETER_NAME)))
423+
.merge(relationshipFragment)
424+
.returning(Functions.id(relationshipFragment))
425+
.build();
426+
}
397427

398428
@NonNull
399429
public Statement prepareSaveOfRelationshipWithProperties(Neo4jPersistentEntity<?> neo4jPersistentEntity,
@@ -433,6 +463,48 @@ public Statement prepareSaveOfRelationshipWithProperties(Neo4jPersistentEntity<?
433463
.build();
434464
}
435465

466+
@NonNull
467+
public Statement prepareUpdateOfRelationshipsWithProperties(Neo4jPersistentEntity<?> neo4jPersistentEntity,
468+
RelationshipDescription relationship, boolean isNew) {
469+
470+
Assert.isTrue(relationship.hasRelationshipProperties(),
471+
"Properties required to create a relationship with properties");
472+
473+
Node startNode = node(neo4jPersistentEntity.getPrimaryLabel(), neo4jPersistentEntity.getAdditionalLabels()).named(START_NODE_NAME);
474+
Node endNode = anyNode(END_NODE_NAME);
475+
String idPropertyName = neo4jPersistentEntity.getRequiredIdProperty().getPropertyName();
476+
477+
String type = relationship.getType();
478+
479+
Relationship relationshipFragment = (
480+
relationship.isOutgoing() ?
481+
startNode.relationshipTo(endNode, type) :
482+
startNode.relationshipFrom(endNode, type))
483+
.named(RELATIONSHIP_NAME);
484+
485+
String row = "row";
486+
Property relationshipProperties = Cypher.property(row, Constants.NAME_OF_PROPERTIES_PARAM);
487+
Property idProperty = Cypher.property(row, Constants.FROM_ID_PARAMETER_NAME);
488+
StatementBuilder.OngoingReadingWithWhere matchStartAndEndNode =
489+
Cypher.unwind(parameter(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM)).as(row)
490+
.with(row)
491+
.match(startNode)
492+
.where(neo4jPersistentEntity.isUsingInternalIds() ? startNode.internalId().isEqualTo(idProperty)
493+
: startNode.property(idPropertyName).isEqualTo(idProperty))
494+
.match(endNode).where(endNode.internalId().isEqualTo(Cypher.property(row, Constants.TO_ID_PARAMETER_NAME)));
495+
496+
StatementBuilder.ExposesSet createOrUpdateRelationship = isNew
497+
? matchStartAndEndNode.create(relationshipFragment)
498+
: matchStartAndEndNode.match(relationshipFragment)
499+
.where(Functions.id(relationshipFragment).isEqualTo(Cypher.property(row, Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM)));
500+
501+
if (isNew) {
502+
return createOrUpdateRelationship.mutate(RELATIONSHIP_NAME, relationshipProperties).returning(Functions.id(relationshipFragment)).build();
503+
}
504+
505+
return createOrUpdateRelationship.mutate(RELATIONSHIP_NAME, relationshipProperties).build();
506+
}
507+
436508
@NonNull
437509
public Statement prepareDeleteOf(
438510
Neo4jPersistentEntity<?> neo4jPersistentEntity,

0 commit comments

Comments
 (0)