Skip to content

Commit c4e418a

Browse files
committed
GH-2593 - Move relationship updates to batch operations.
This change explicitly excludes: - All reactive operations - Dynamic relationships Closes #2593
1 parent 935958e commit c4e418a

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
@@ -770,6 +770,11 @@ private <T> T processNestedRelations(
770770
Neo4jPersistentProperty relationshipProperty = association.getInverse();
771771

772772
RelationshipHandler relationshipHandler = RelationshipHandler.forProperty(relationshipProperty, rawValue);
773+
List<Object> plainRelationshipRows = new ArrayList<>();
774+
List<Map<String, Object>> relationshipPropertiesRows = new ArrayList<>();
775+
List<Map<String, Object>> newRelationshipPropertiesRows = new ArrayList<>();
776+
List<Object> updateRelatedValuesToStore = new ArrayList<>();
777+
List<Object> newRelatedValuesToStore = new ArrayList<>();
773778

774779
for (Object relatedValueToStore : relatedValuesToStore) {
775780

@@ -818,28 +823,60 @@ private <T> T processNestedRelations(
818823

819824
Object idValue = idProperty != null
820825
? relationshipContext
821-
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore).getProperty(idProperty)
826+
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore).getProperty(idProperty)
822827
: null;
823828

829+
Map<String, Object> properties = new HashMap<>();
830+
properties.put(Constants.FROM_ID_PARAMETER_NAME, convertIdValues(sourceEntity.getRequiredIdProperty(), fromId));
831+
properties.put(Constants.TO_ID_PARAMETER_NAME, relatedInternalId);
832+
properties.put(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM, idValue);
824833
boolean isNewRelationship = idValue == null;
825-
826-
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatement(
827-
sourceEntity, relationshipContext, relatedValueToStore, isNewRelationship);
828-
829-
Optional<Long> relationshipInternalId = neo4jClient.query(renderer.render(statementHolder.getStatement()))
830-
.bind(convertIdValues(sourceEntity.getRequiredIdProperty(), fromId)) //
831-
.to(Constants.FROM_ID_PARAMETER_NAME) //
832-
.bind(relatedInternalId) //
833-
.to(Constants.TO_ID_PARAMETER_NAME) //
834-
.bind(idValue) //
835-
.to(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM) //
836-
.bindAll(statementHolder.getProperties())
837-
.fetchAs(Long.class).one();
838-
839-
if (idProperty != null && isNewRelationship) {
840-
relationshipContext
841-
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore)
842-
.setProperty(idProperty, relationshipInternalId.get());
834+
if (relationshipDescription.isDynamic()) {
835+
// create new dynamic relationship properties
836+
if (relationshipDescription.hasRelationshipProperties() && isNewRelationship && idProperty != null) {
837+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
838+
sourceEntity, relationshipDescription, relatedValueToStore, true);
839+
840+
List<Object> row = Collections.singletonList(properties);
841+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, row);
842+
Optional<Long> relationshipInternalId = neo4jClient.query(renderer.render(statementHolder.getStatement()))
843+
.bind(convertIdValues(sourceEntity.getRequiredIdProperty(), fromId)) //
844+
.to(Constants.FROM_ID_PARAMETER_NAME) //
845+
.bind(relatedInternalId) //
846+
.to(Constants.TO_ID_PARAMETER_NAME) //
847+
.bind(idValue) // always null
848+
.to(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM) //
849+
.bindAll(statementHolder.getProperties())
850+
.fetchAs(Long.class).one();
851+
assignIdToRelationshipProperties(relationshipContext, relatedValueToStore, idProperty, relationshipInternalId.get());
852+
} else { // plain (new or to update) dynamic relationship or dynamic relationships with properties to update
853+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
854+
sourceEntity, relationshipDescription, relatedValueToStore, false);
855+
856+
List<Object> row = Collections.singletonList(properties);
857+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, row);
858+
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+
.run();
867+
}
868+
} else if (relationshipDescription.hasRelationshipProperties() && isNewRelationship && idProperty != null) {
869+
newRelationshipPropertiesRows.add(properties);
870+
newRelatedValuesToStore.add(relatedValueToStore);
871+
} else if (relationshipDescription.hasRelationshipProperties()) {
872+
neo4jMappingContext.getEntityConverter().write(
873+
((MappingSupport.RelationshipPropertiesWithEntityHolder) relatedValueToStore).getRelationshipProperties(),
874+
properties);
875+
876+
relationshipPropertiesRows.add(properties);
877+
} else {
878+
// non-dynamic relationship or relationship with properties
879+
plainRelationshipRows.add(properties);
843880
}
844881

845882
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
@@ -854,6 +891,37 @@ private <T> T processNestedRelations(
854891

855892
relationshipHandler.handle(relatedValueToStore, relatedObjectBeforeCallbacksApplied, potentiallyRecreatedNewRelatedObject);
856893
}
894+
// batch operations
895+
if (!(relationshipDescription.hasRelationshipProperties() || relationshipDescription.isDynamic() || plainRelationshipRows.isEmpty())) {
896+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeSimpleRelationshipBatch(
897+
sourceEntity, relationshipDescription, plainRelationshipRows);
898+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, plainRelationshipRows);
899+
neo4jClient.query(renderer.render(statementHolder.getStatement()))
900+
.bindAll(statementHolder.getProperties())
901+
.run();
902+
} else if (relationshipDescription.hasRelationshipProperties()) {
903+
if (!relationshipPropertiesRows.isEmpty()) {
904+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeRelationshipsWithPropertiesBatch(false,
905+
sourceEntity, relationshipDescription, updateRelatedValuesToStore, relationshipPropertiesRows);
906+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, relationshipPropertiesRows);
907+
908+
neo4jClient.query(renderer.render(statementHolder.getStatement()))
909+
.bindAll(statementHolder.getProperties())
910+
.run();
911+
} else if (!newRelatedValuesToStore.isEmpty()) {
912+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeRelationshipsWithPropertiesBatch(true,
913+
sourceEntity, relationshipDescription, newRelatedValuesToStore, newRelationshipPropertiesRows);
914+
915+
List<Long> all = new ArrayList<>(neo4jClient.query(renderer.render(statementHolder.getStatement()))
916+
.bindAll(statementHolder.getProperties())
917+
.fetchAs(Long.class).all());
918+
// assign new ids
919+
for (int i = 0; i < all.size(); i++) {
920+
Long aLong = all.get(i);
921+
assignIdToRelationshipProperties(relationshipContext, newRelatedValuesToStore.get(i), idProperty, aLong);
922+
}
923+
}
924+
}
857925

858926
relationshipHandler.applyFinalResultToOwner(propertyAccessor);
859927
});
@@ -863,6 +931,12 @@ private <T> T processNestedRelations(
863931
return finalSubgraphRoot;
864932
}
865933

934+
private void assignIdToRelationshipProperties(NestedRelationshipContext relationshipContext, Object relatedValueToStore, Neo4jPersistentProperty idProperty, Long relationshipInternalId) {
935+
relationshipContext
936+
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore)
937+
.setProperty(idProperty, relationshipInternalId);
938+
}
939+
866940
private Entity saveRelatedNode(Object entity, NodeDescription<?> targetNodeDescription, PropertyFilter includeProperty, PropertyFilter.RelaxedPropertyPath currentPropertyPath) {
867941

868942
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
@@ -974,9 +974,16 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
974974
: null;
975975

976976
boolean isNewRelationship = idValue == null;
977-
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatement(
978-
sourceEntity, relationshipContext, relatedValueToStore, isNewRelationship);
979-
977+
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
978+
sourceEntity, relationshipDescription, relatedValueToStore, isNewRelationship);
979+
980+
Map<String, Object> properties = new HashMap<>();
981+
properties.put(Constants.FROM_ID_PARAMETER_NAME, convertIdValues(sourceEntity.getRequiredIdProperty(), fromId));
982+
properties.put(Constants.TO_ID_PARAMETER_NAME, relatedInternalId);
983+
properties.put(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM, idValue);
984+
List<Object> rows = new ArrayList<>();
985+
rows.add(properties);
986+
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, rows);
980987
// in case of no properties the bind will just return an empty map
981988
return neo4jClient
982989
.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
@@ -386,6 +386,36 @@ public Statement prepareSaveOfRelationship(Neo4jPersistentEntity<?> neo4jPersist
386386
.returning(Functions.id(relationshipFragment))
387387
.build();
388388
}
389+
@NonNull
390+
public Statement prepareSaveOfRelationships(Neo4jPersistentEntity<?> neo4jPersistentEntity,
391+
RelationshipDescription relationship, @Nullable String dynamicRelationshipType) {
392+
393+
final Node startNode = neo4jPersistentEntity.isUsingInternalIds()
394+
? anyNode(START_NODE_NAME)
395+
: node(neo4jPersistentEntity.getPrimaryLabel(), neo4jPersistentEntity.getAdditionalLabels())
396+
.named(START_NODE_NAME);
397+
398+
final Node endNode = anyNode(END_NODE_NAME);
399+
String idPropertyName = neo4jPersistentEntity.getRequiredIdProperty().getPropertyName();
400+
401+
String type = relationship.isDynamic() ? dynamicRelationshipType : relationship.getType();
402+
Relationship relationshipFragment = (relationship.isOutgoing() ?
403+
startNode.relationshipTo(endNode, type) :
404+
startNode.relationshipFrom(endNode, type)).named(RELATIONSHIP_NAME);
405+
406+
String row = "relationship";
407+
Property idProperty = Cypher.property(row, Constants.FROM_ID_PARAMETER_NAME);
408+
return Cypher.unwind(parameter(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM)).as(row)
409+
.with(row)
410+
.match(startNode)
411+
.where(neo4jPersistentEntity.isUsingInternalIds()
412+
? startNode.internalId().isEqualTo(idProperty)
413+
: startNode.property(idPropertyName).isEqualTo(idProperty))
414+
.match(endNode).where(endNode.internalId().isEqualTo(Cypher.property(row, Constants.TO_ID_PARAMETER_NAME)))
415+
.merge(relationshipFragment)
416+
.returning(Functions.id(relationshipFragment))
417+
.build();
418+
}
389419

390420
@NonNull
391421
public Statement prepareSaveOfRelationshipWithProperties(Neo4jPersistentEntity<?> neo4jPersistentEntity,
@@ -425,6 +455,48 @@ public Statement prepareSaveOfRelationshipWithProperties(Neo4jPersistentEntity<?
425455
.build();
426456
}
427457

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

0 commit comments

Comments
 (0)