neo4jcypherspring-data-neo4j-4neo4j-ogm

Neo4j SDN4 repository method with custom query and composite entity


I have the following SDN 4 entity:

@NodeEntity
public class Decision {


    @Relationship(type = CONTAINS, direction = Relationship.INCOMING)
    private Set<Decision> parentDecisions;

...
}

I'd like to find this entity by id and return all its parentDecisions with a following SDN4 repository method and the custom Cypher query:

MATCH (d:Decision) WHERE d.id = {decisionId} OPTIONAL MATCH (d)<-[rdp:CONTAINS]-(parentD:Decision) RETURN d, rdp, parentD
Decision getById(@Param("decisionId") Long decisionId);

right now in case of existing parentD it fails with the following exception:

java.lang.RuntimeException: Result not of expected size. Expected 1 row but found 3
    at org.neo4j.ogm.session.delegates.ExecuteQueriesDelegate.queryForObject(ExecuteQueriesDelegate.java:73)
    at org.neo4j.ogm.session.Neo4jSession.queryForObject(Neo4jSession.java:382)
    at sun.reflect.GeneratedMethodAccessor111.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

How to properly implement this logic with SDN4 repository method and the custom Cypher query ?

UPDATED

I tested approach suggested by frant.hartm. Unfortunately it still doesn't work.

For example the following SDN4 repository method correctly returns 2 Decision:

@Query("MATCH (d:Decision)-[drd:FOLLOWS]->(following:Decision) WHERE d.id = {decisionId} RETURN following")
List<Decision> test(@Param("decisionId") Long decisionId);

but the next one:

@Query("MATCH (d:Decision) WHERE d.id = {decisionId} OPTIONAL MATCH (d)-[rdf:FOLLOWS]->(followD:Decision) RETURN d as decision, collect(rdf), collect(followD) ")
DecisionHolder test1(@Param("decisionId") Long decisionId);

returns only main decision (holder.getDecision()) but with empty collection of followD (holder.getDecision().getFollowDecisions()) Decision.

This is Decision entity:

@NodeEntity
public class Decision {

    private static final String FOLLOWS = "FOLLOWS";

    @Relationship(type = FOLLOWS, direction = Relationship.INCOMING)
    private Set<Decision> followDecisions;

    @Relationship(type = FOLLOWS, direction = Relationship.INCOMING)
    public Set<Decision> getFollowDecisions() {
        return followDecisions;
    }

    @Relationship(type = FOLLOWS, direction = Relationship.INCOMING)
    public void setFollowDecisions(Set<Decision> followDecisions) {
        this.followDecisions = followDecisions;
    }

....

}

What am I doing wrong ?


Solution

  • The core issue is that your query returns multiple Decision instances (under different variables - d and parentD), so it can't just arbitrarily chose one. Use @QueryResult to extract desired value. Also use COLLECT to get a single result.

    @QueryResult
    public class DecisionHolder {
        Decision d;
    }
    
    MATCH (d:Decision) WHERE d.id = {decisionId} " + 
        "OPTIONAL MATCH (d)<-[rdp:CONTAINS]-(parentD:Decision) " +
        "RETURN d, collect(rdp), collect(parentD)")
    DecisionHolder getById(@Param("decisionId") Long decisionId);
    

    Update:

    For the relationships to map correctly relationship entities and simple relationships with same type must not be mixed.

    If you have a simple relationship and relationship entity with same type