cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

Interface-based projection not working

theFrank
Node

Hi, I'm running into a weird problem.

Specifically, I have defined an interface-based projection to retrieve a view of a persisted entity (as per https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#projections.interfaces) from the database through a custom query (which according to this: https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#faq.custom-queries-and-custom-... shouldn't be a problem, given I don't use this query to write but only to read from the db). However, when I try to run the query from the backend, I get the following error: 

 

 

org.springframework.data.neo4j.core.mapping.NoRootNodeMappingException: Could not find mappable nodes or relationships inside Record <{name: "Sara", age: 27, sex: "M", occupation: "Worker"}> for org.springframework.data.neo4j.core.mapping.DefaultNeo4jPersistentEntity@30aec673

 

 

Here is my custom query (inside a Neo4j Spring repository with root entity Student):

 

 

@Query("MATCH (students:Students)-[:LIKES]->()<-[:TEACHES]-(teacher: Teacher {email : $email}) "
+ "WHERE NOT (teacher)-->(students) "
+ "RETURN DISTINCT students.name, students.age, students.sex, students.occupation")
List<StudentCandidate> findUnmetLikingStudents(String email);

 

 

here is my StudentCandidate (the projection interface):

 

 

public interface StudentCandidate {

String getName();

int getAge();

String getSex();

String getOccupation();

}

 

 

and here's my Student class (with most details edited out for brevity's sake):

 

 

@Node
public class Student extends Person implements Positionable {

  @Id
  private String id;
  private double latitude;
  private double longitude;
  private int maxDistance;
  private Sex roommateSex;
  private int roommateAge;

  public Student(String username, String password, boolean enabled, boolean accountNonExpired,
      boolean credentialsNonExpired, boolean accountNonLocked, String email, String name, int age,
      String sex, String occupation, double latitude, double longitude, int maxDistance, String roommateSex, int roommateAge) {
    super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
        new HashSet<>(Arrays.asList(new UserAuthority(UserRole.STUDENT))), email, name, age, sex,
        occupation);
    id = UUID.randomUUID().toString();
    this.latitude = latitude;
    this.longitude = longitude;
    this.maxDistance = maxDistance;
    this.setRoommateSex(roommateSex);
    this.roommateAge = roommateAge;
  }

 

 

as you may see, some properties (including those in the interface projection) are actually set in the Person superclass (N.B. they're still called the same, i.e. "name", "age", "sex" and "occupation". Also, "sex" and "occupation" are enumerations, but even when I set the corresponding accessor return types in StudentCandidate to the enumerations - rather than String -, I get the same error). I don't know if that's relevant.

Is there something I'm doing wrong or is this functionality not actually supported?

1 ACCEPTED SOLUTION

The problem is the result pattern of your custom query. You are trying to make Spring Data Neo4j map some "arbitrary" values but it expects nodes to map also for projections. 
If you are using projections without a custom query, the load mechanism creates a map structure (https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#query-creation.load) that is an alternative for the node representation. But this is something you would have to decide: Either load all properties of a node and have a cleaner query or reduce the loaded properties by hand and have a more complex query.

MATCH (students:Students)-[:LIKES]->()<-[:TEACHES]-(teacher: Teacher {email : $email})
WHERE NOT (teacher)-->(students)
RETURN DISTINCT students.name, students.age, students.sex, students.occupation

should be more like

MATCH (students:Students)-[:LIKES]->()<-[:TEACHES]-(teacher: Teacher {email : $email})
WHERE NOT (teacher)-->(students)
RETURN DISTINCT students

 

 

View solution in original post

5 REPLIES 5

The problem is the result pattern of your custom query. You are trying to make Spring Data Neo4j map some "arbitrary" values but it expects nodes to map also for projections. 
If you are using projections without a custom query, the load mechanism creates a map structure (https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#query-creation.load) that is an alternative for the node representation. But this is something you would have to decide: Either load all properties of a node and have a cleaner query or reduce the loaded properties by hand and have a more complex query.

MATCH (students:Students)-[:LIKES]->()<-[:TEACHES]-(teacher: Teacher {email : $email})
WHERE NOT (teacher)-->(students)
RETURN DISTINCT students.name, students.age, students.sex, students.occupation

should be more like

MATCH (students:Students)-[:LIKES]->()<-[:TEACHES]-(teacher: Teacher {email : $email})
WHERE NOT (teacher)-->(students)
RETURN DISTINCT students

 

 

Hi and thanks! I understood the problem, but I'm not sure about the solution: if I adopt the second query (which still *is* a custom query in a Spring Data perspective, so does that change anything to what you said here:

If you are using projections without a custom query, the load mechanism creates a map structure (https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#query-creation.load) that is an alternative for the node representation.

?), will the data selection happen in the database or in the backend (meaning I send a whole load of useless data to my server)?

While we're at it: I imagine that even in the former case, using the Neo4jClient would be less computationally intensive because Neo4j wouldn't have to build a projection map, right?

Thanks again!

Perfect summarised my point " Either load all properties of a node " with "whole load of useless data" (for your use-case). 😁 This is exactly what would happen, if you do not mimic _our_ queries with the map pattern plus the special fields like labels etc.

And also a good observation is to pull the Neo4jClient in for this. I mean if we really just talk about a few fields and no relationships to map, this is a valid option.
We've created and made it public exactly for this.
Also you don't have to worry about Spring transactions because the Neo4jClient will also participate in those ongoing transactions. (You could also mix repositories, Neo4jTemplate and Neo4jClient in one Spring transaction and end up in the same DB transaction).

Absolutely great, got it and thank you! If you want to, you may also answer the question on StackOverflow. Otherwise, I'll answer it myself quoting and linking to your message here.

There is already a minimal answer. I think it would be best, if you answer this yourself how it fixed _your_ problem.