2

CONTEXT:

I am working on a java spring web application backed by a Neo4j database. I have an object "Student" that has taken a number of "Modules" and "Courses". The relationship is connected via "rich relationship" nodes "RR_TakenModule" and "RR_TakenCourse" that specify a grade property.

public class Student extends DomainObject {
    String fullName;
    Gender gender;
    Collection<RR_TakenModule> modulesTaken;
    Collection<RR_TakenCourse> coursesTaken;
    DateTime yearStarted;

that way I could ask for a Student that got 74% in a Module

PROBLEM:

I cannot return a "deep" object, from the GraphRepository set up. Ie I cannot return a Student with populated "modulesTaken" and "coursesTaken" properties. I've seen several approaches online such as trying the cypher query :

MATCH (student:Student) -[:MODULES_TAKEN]-> (rr:RR_TakenModule) -[:MODULE]-> (m:Module) RETURN student, COLLECT(rr) as modulesTaken

Which is claimed to map the RR_TakenModules into the object dynamically via the property name. It does not do this for me and returns a "Error mapping GraphModel to instance of com.domain.Actors.Student" error code. Although note that it does properly group when running the cypher query in the localhost:7474 interface. Clearly mapping is the issue.

Currently I have adopted the template and Map approach. Using Neo4jOperations and the Result object. This works, however means that I have to write out iterators that go through and assign values based on the key/value pairs in the result object. This leads to high maintenance and a larger chance of errors.

Looking around their used to be options such as @Fetch, and specifying depth in Neo4jTemplate queries, however non of these methods seem to be present in my version (most appear to be depreciated)

QUESTION :

Is there a way to map sub-objects (ie "collection prop" and "set prop") of a Neo4j entity object via Graph Repositories. I realise there are predefined methods such as "findOne" that have a depth parameter, but I want to apply a similar implementation to my custom queries.

Alternatively, is there a solution for dynamically mapping a Neo4jOperations Result object to a java object without defining some custom json parser

VERSIONS :

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.1.RELEASE</version>
</parent>

<properties>
    <java.version>1.8</java.version>
    <spring-data-neo4j.version>4.0.0.RELEASE</spring-data-neo4j.version>
    <spring-data-releasetrain.version>Gosling-RELEASE</spring-data-releasetrain.version>
</properties>
Jack Dalton
  • 3,536
  • 5
  • 23
  • 40
  • 4.1.1.RELEASE has better support for mapping entities from custom queries. Please upgrade to this release. More info here: http://graphaware.com/neo4j/2016/04/06/mapping-query-entities-sdn.html – Luanne Apr 08 '16 at 03:38

2 Answers2

1

Spring Data Neo4j (SDN) does not populate the referenced (by relationship) nodes in the returned node. It will, however populate the fullName and gender properties (assuming here that Gender is an Enum such that a string can be easily mapped).

In your query above you return a Student node and a collection of RR_TakenModule, but Spring does not know how to map this since there's no class that comprises these two results. You need to define an explicit result for this case. I suggest the following:

@NodeEntity(label = "Student")
public class Student extends DomainObject {
    @Property(name = "studentId")
    private String studentId; //you want to have this
    @Property(name = "fullName")
    private String fullName;
    @Property(name = "gender")
    private Gender gender; //assuming its an Enum
    @Relationship(type = "MODULES_TAKEN") // Relationship.OUTGOING is default
    private Set<RR_TakenModule) modulesTaken;
}

Then define a repository

public interface StudentRepository extends GraphRepository<Student> {

    @Query("MATCH (s:Student {studentId:{studentId}})-[:MODULES_TAKEN]->(m:RR_TakenModule) RETURN s AS student, collect(DISTINCT m) AS modulesTaken")
    public StudentWithModulesTakenResult getByUserIdWithModulesTaken(@Param("studentId") String studentId)
}

and the respective result

@QueryResult
public class StudentWithModulesTakenResult {
    private Student student;
    private Set<RR_TakenModule> modulesTaken;
    //only getters, no setters
}
Christoph Möbius
  • 1,352
  • 1
  • 12
  • 18
0

After looking into the default "findOne(nodeid, depth)" method GraphRepositor offers I have found a solution that works for me.

I format the query as MATCH (n:Entity) WITH n MATCH p=(n)-[*0..4]->(m) RETURN p where Entity is the name of your base entity. I cannot justify why, but this format will dynamically map the result to your respective POJO.

Note you can specify the depth (*0..4) to specify how deep a POJO is populated. It is also worth noting that the relationship is using -[]-> not -[]- which is the default relationship for findOne(nodeid, depth). If this does not work it might be possible that you have not got the latest Neo4j OGM version.

Jack Dalton
  • 3,536
  • 5
  • 23
  • 40