From c75df632caa4884b3814f8a7e3e6985702eed49b Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Mon, 19 Apr 2021 15:13:25 +0200 Subject: [PATCH] GH-2210 - Improve documentation for hydrating collections based on queries returning paths. This closes #2210. --- .../asciidoc/appendix/custom-queries.adoc | 46 ++++ src/main/asciidoc/faq/faq.adoc | 2 + src/main/asciidoc/img/custom-query.paths.png | Bin 0 -> 14799 bytes .../integration/issues/gh2210/GH2210IT.java | 255 ++++++++++++++++++ 4 files changed, 303 insertions(+) create mode 100644 src/main/asciidoc/img/custom-query.paths.png create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2210/GH2210IT.java diff --git a/src/main/asciidoc/appendix/custom-queries.adoc b/src/main/asciidoc/appendix/custom-queries.adoc index 9bd0fc797..069172bc5 100644 --- a/src/main/asciidoc/appendix/custom-queries.adoc +++ b/src/main/asciidoc/appendix/custom-queries.adoc @@ -125,6 +125,52 @@ NOTE: Deciding if you want to go with client-side or database-side reduction dep All the paths needs to get created in the database's memory first when the `reduce` function is used. On the other hand a large amount of data that needs to get merged on the client-side results in a higher memory usage there. +[[custom-query.paths]] +== Using paths to populate and return a list of entities + +Given are a graph that looks like this: + +[[custom-query.paths.g]] +.graph with outgoing relationships +image::custom-query.paths.png[] + +and a domain model as shown in the <> (Constructors and accessors have been omitted for brevity): + +[[custom-query.paths.dm]] +[source,java,indent=0,tabsize=4] +.Domain model for a <>. +---- +include::../../../../src/test/java/org/springframework/data/neo4j/integration/issues/gh2210/GH2210IT.java[tag=custom-query.paths.dm] +---- + +As you see, the relationships are only outgoing. Generated finder methods (including `findById`) will always try to match +a root node to be mapped. From there on onwards, all related objects will be mapped. In queries that should return only one object, +that root object is returned. In queries that return many objects, all matching objects are returned. Out- and incoming relationships +from those objects returned are of course populated. + +Assume the following Cypher query: + +[source,cypher] +---- +MATCH p = (leaf:SomeEntity {number: $a})-[:SOME_RELATION_TO*]-(:SomeEntity) +RETURN leaf, collect(nodes(p)), collect(relationships(p)) +---- + +It follows the recommendation from <> and it works great for the leaf node +you want to match here. However: That is only the case in all scenarios that return 0 or 1 mapped objects. +While that query will populate all relationships like before, it won't return all 4 objects. + +This can be changed by returning the whole path: + +[source,cypher] +---- +MATCH p = (leaf:SomeEntity {number: $a})-[:SOME_RELATION_TO*]-(:SomeEntity) +RETURN p +---- + +Here we do want to use the fact that the path `p` actually returns 3 rows with paths to all 4 nodes. All 4 nodes will be +populated, linked together and returned. + [[custom-queries.parameters]] == Parameters in custom queries diff --git a/src/main/asciidoc/faq/faq.adoc b/src/main/asciidoc/faq/faq.adoc index 9db43508f..61f8c33d4 100644 --- a/src/main/asciidoc/faq/faq.adoc +++ b/src/main/asciidoc/faq/faq.adoc @@ -550,6 +550,8 @@ The query returns the path plus all relationships and related nodes collected so The path mapping works for single paths as well for multiple records of paths (which are returned by the `allShortestPath` function.) +TIP: Named paths can be used efficiently to populate and return more than just a root node, see <>. + [[faq.spring-boot.sdn]] == Do I need Spring Boot to use Spring Data Neo4j? diff --git a/src/main/asciidoc/img/custom-query.paths.png b/src/main/asciidoc/img/custom-query.paths.png new file mode 100644 index 0000000000000000000000000000000000000000..ff7f0b5547e0185c37b9906144cca871b026d5dc GIT binary patch literal 14799 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 literal 0 HcmV?d00001 diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2210/GH2210IT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2210/GH2210IT.java new file mode 100644 index 000000000..577286f18 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2210/GH2210IT.java @@ -0,0 +1,255 @@ +/* + * Copyright 2011-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2210; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Record; +import org.neo4j.driver.Transaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.config.AbstractNeo4jConfig; +import org.springframework.data.neo4j.core.Neo4jTemplate; +import org.springframework.data.neo4j.core.convert.Neo4jConversions; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; +import org.springframework.data.neo4j.core.schema.TargetNode; +import org.springframework.data.neo4j.test.Neo4jExtension; +import org.springframework.data.neo4j.test.Neo4jIntegrationTest; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Michael J. Simons + */ +@Neo4jIntegrationTest +class GH2210IT { + + protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport; + + static final Long numberA = 1L; + static final Long numberB = 2L; + static final Long numberC = 3L; + static final Long numberD = 4L; + + @BeforeAll + protected static void setupData() { + try (Transaction transaction = neo4jConnectionSupport.getDriver().session().beginTransaction()) { + transaction.run("MATCH (n) detach delete n"); + Map params = new HashMap<>(); + params.put("numberA", numberA); + params.put("numberB", numberB); + params.put("numberC", numberC); + params.put("numberD", numberD); + Record r = transaction.run("create (a:SomeEntity {number: $numberA, name: \"A\"})\n" + + "create (b:SomeEntity {number: $numberB, name: \"B\"})\n" + + "create (c:SomeEntity {number: $numberC, name: \"C\"})\n" + + "create (d:SomeEntity {number: $numberD, name: \"D\"})\n" + + "create (a) -[:SOME_RELATION_TO {someData: \"d1\"}] -> (b)\n" + + "create (b) <-[:SOME_RELATION_TO {someData: \"d2\"}] - (c)\n" + + "create (c) <-[:SOME_RELATION_TO {someData: \"d3\"}] - (d)\n" + + "return * ", params).single(); + transaction.commit(); + } + } + + @Test // GH-2210 + void standardFinderShouldWork(@Autowired Neo4jTemplate template) { + + assertA(template.findById(numberA, SomeEntity.class)); + + assertB(template.findById(numberB, SomeEntity.class)); + + assertD(template.findById(numberD, SomeEntity.class)); + } + + @Test // GH-2210 + void pathsBasedQueryShouldWork(@Autowired Neo4jTemplate template) { + + String query = "MATCH p = (leaf:SomeEntity {number: $a})-[:SOME_RELATION_TO*]-(:SomeEntity) RETURN leaf, collect(nodes(p)), collect(relationships(p))"; + assertA(template.findOne(query, Collections.singletonMap("a", numberA), SomeEntity.class)); + + assertB(template.findOne(query, Collections.singletonMap("a", numberB), SomeEntity.class)); + + assertD(template.findOne(query, Collections.singletonMap("a", numberD), SomeEntity.class)); + } + + @Test // GH-2210 + void aPathReturnedShouldPopulateAllNodes(@Autowired Neo4jTemplate template) { + + String query = "MATCH p = (leaf:SomeEntity {number: $a})-[:SOME_RELATION_TO*]-(:SomeEntity) RETURN p"; + assertAll(template.findAll(query, Collections.singletonMap("a", numberA), SomeEntity.class)); + } + + @Test // GH-2210 + void standardFindAllShouldWork(@Autowired Neo4jTemplate template) { + + assertAll(template.findAll(SomeEntity.class)); + } + + void assertAll(List entities) { + + assertThat(entities).hasSize(4); + assertThat(entities).allSatisfy(v -> { + switch (v.getName()) { + case "A": + assertA(Optional.of(v)); + break; + case "B": + assertB(Optional.of(v)); + break; + case "D": + assertD(Optional.of(v)); + break; + } + }); + } + + void assertA(Optional a) { + + assertThat(a).hasValueSatisfying(s -> { + assertThat(s.getName()).isEqualTo("A"); + assertThat(s.getSomeRelationsOut()) + .hasSize(1) + .first().satisfies(b -> { + assertThat(b.getSomeData()).isEqualTo("d1"); + assertThat(b.getTargetPerson().getName()).isEqualTo("B"); + assertThat(b.getTargetPerson().getSomeRelationsOut()).isEmpty(); + }); + }); + } + + void assertD(Optional d) { + + assertThat(d).hasValueSatisfying(s -> { + assertThat(s.getName()).isEqualTo("D"); + assertThat(s.getSomeRelationsOut()) + .hasSize(1) + .first().satisfies(c -> { + assertThat(c.getSomeData()).isEqualTo("d3"); + assertThat(c.getTargetPerson().getName()).isEqualTo("C"); + assertThat(c.getTargetPerson().getSomeRelationsOut()) + .hasSize(1) + .first().satisfies(b -> { + assertThat(b.getSomeData()).isEqualTo("d2"); + assertThat(b.getTargetPerson().getName()).isEqualTo("B"); + assertThat(b.getTargetPerson().getSomeRelationsOut()).isEmpty(); + }); + }); + }); + } + + void assertB(Optional b) { + + assertThat(b).hasValueSatisfying(s -> { + assertThat(s.getName()).isEqualTo("B"); + assertThat(s.getSomeRelationsOut()).isEmpty(); + }); + } + + // tag::custom-query.paths.dm[] + @Node + static class SomeEntity { + + @Id + private final Long number; + + private String name; + + @Relationship(type = "SOME_RELATION_TO", direction = Relationship.Direction.OUTGOING) + private Set someRelationsOut = new HashSet<>(); + // end::custom-query.paths.dm[] + + public Long getNumber() { + return number; + } + + public String getName() { + return name; + } + + public Set getSomeRelationsOut() { + return someRelationsOut; + } + + SomeEntity(Long number) { + this.number = number; + } + // tag::custom-query.paths.dm[] + } + + @RelationshipProperties + static class SomeRelation { + + @Id @GeneratedValue + private Long id; + + private String someData; + + @TargetNode + private SomeEntity targetPerson; + // end::custom-query.paths.dm[] + + public Long getId() { + return id; + } + + public String getSomeData() { + return someData; + } + + public SomeEntity getTargetPerson() { + return targetPerson; + } + // tag::custom-query.paths.dm[] + } + // end::custom-query.paths.dm[] + + @Configuration + @EnableTransactionManagement + static class Config extends AbstractNeo4jConfig { + + @Bean + public Driver driver() { + + return neo4jConnectionSupport.getDriver(); + } + + @Override + public Neo4jMappingContext neo4jMappingContext(Neo4jConversions neo4JConversions) throws ClassNotFoundException { + + Neo4jMappingContext ctx = new Neo4jMappingContext(neo4JConversions); + ctx.setInitialEntitySet(new HashSet<>(Arrays.asList(SomeEntity.class, SomeRelation.class))); + return ctx; + } + } +}