4

Let's suppose we use this simple graph model:

@NodeEntity
class Ad {    
    // GraphId and more Ad attributes...

    @Relationship(type = "HAS_ADVERTISER", direction = Relationship.OUTGOING)
    private User advertiser;

    @Relationship(type = "IS_FAVOURITE_TO", direction = Relationship.OUTGOING)
    private Set<User> favourites = new HashSet<>();

    // Getters and setters
 }

EDIT: Following the advises of one of the aswers, I've tried adding the incoming relationships to User entity, but the problem still remains.

@NodeEntity
class User {
    // User attributes. No incoming relationships for HAS_ADVERTISER neither IS_FAVOURITE_TO...

   // EDIT: Added incoming relationships to User entity, but the problem still remains. Tried annotating getters/setters, but neither works
   @Relationship(type = "HAS_ADVERTISER", direction = Relationship.INCOMING)
   private Set<Ad> ads = new HashSet<>();

   @Relationship(type = "IS_FAVOURITE_TO", direction = Relationship.INCOMING)
   private Set<Ad> favourites = new HashSet<>();

   @Relationship(direction = Relationship.INCOMING)
   public Set<Ad> getAds() {
       return ads;
   }

   @Relationship(direction = Relationship.INCOMING)
   public void setAds(Set<Ad> ads) {
       this.ads = ads;
   }

   @Relationship(direction = Relationship.INCOMING)
   public Set<Ad> getFavourites() {
       return favourites;
   }

   @Relationship(direction = Relationship.INCOMING)
   public void setFavourites(Set<Ad> favourites) {
       this.favourites = favourites;
   }
}

If I execute a cypher query to retrieve the ads with advertiser and favourites information through neo4j console, it's simply working well:

MATCH (ad:Ad),
(ad)-[has_advertiser:HAS_ADVERTISER]->(advertiser:User),
(ad)-[is_favourite_to: IS_FAVOURITE_TO] -> (favouriteUser:User)
return ad, has_advertiser, advertiser, is_favourite_to, favouriteUser

enter image description here

However, if I execute the query through a neo4jRepository instead:

  • advertiser user is persisted correctly.
  • There are two users in favourites set: the advertiser user is always added to the set, which is incorrect because there is no IS_FAVOURITE_TO relationship with this user.

@Repository
public interface AdRepository extends Neo4jRepository<Ad> {

    @Query("MATCH (ad:Ad)," +
            "(ad)-[has_advertiser:HAS_ADVERTISER]->(advertiser:User)," +
            "(ad)-[is_favourite_to: IS_FAVOURITE_TO] -> (favouriteUser:User)" +
            "return ad, has_advertiser, advertiser, " +
            "is_favourite_to, favouriteUser ")
    List<Ad> findAds();
 }

enter image description here

Can I change something in my query or my graph model to avoid this? Maybe a spring-data-neo4j bug?

Versions:

<spring-data-neo4j.version>4.2.0.M1</spring-data-neo4j.version>
<neo4j.ogm.version>2.0.5</neo4j.ogm.version>
troig
  • 7,072
  • 4
  • 37
  • 63

2 Answers2

2

This is due to the fact that the OGM considers this to be an ambiguous model. If you add the incoming relationships to User, your issue should be resolved.

Update: upon further investigation, this is indeed a bug. Opened https://github.com/neo4j/neo4j-ogm/issues/276

The workaround is to annotate all your getters and setters (and make sure the @Relationship annotation contains both the relationship type and the direction) in both Ad and User.

Luanne
  • 19,145
  • 1
  • 39
  • 51
  • Thanks @Luanne, Didn't know that. So If there is a relationship between two nodes, should I always add the relationship to the two nodes? Is this a general rule? I'll try your suggestion in a few hours and let you know iif the issue is solved. Thanks – troig Oct 24 '16 at 08:35
  • When it's ambiguous like in your case, you have two different relationship types to the same entity, one is a 1:1 and one 1:n. In other cases, you need not map both sides. Let me know though if this fixes it – Luanne Oct 24 '16 at 10:33
  • Hi @Luanne. I've just tried with your suggestions and I'm afraid the problem still remains...Any idea? Thanks for your time. NOTE that one relationship is 1:1 and the other one is n:n (not 1:n as you point in your comment). Not sure if it can be related... – troig Oct 24 '16 at 16:16
  • Hmm, when you added the incoming rels, did you annotate both the field and getters/setters with @Relationship specifying INCOMING? (if not, please try that) – Luanne Oct 24 '16 at 16:21
  • Not sure If I've understood correctly (didn't know that getters/setters can also be annotated with @Relationship). I've tried but neither works (please see my question edit) I've also tired adding the relationship name to the getters/setters, but the same problem. Thanks again – troig Oct 24 '16 at 16:38
  • The relationship type on the annotation is required, but you've already tried that. Possible to send us a test case and/or small data sample to try this out? luanne at graphaware dot com – Luanne Oct 25 '16 at 00:49
  • Hi again @Luanne. I've sent you a sample project with a test configuration where you can reproduce the error. Thanks for your time – troig Oct 25 '16 at 19:50
0

edited since I missed the cause of the error completely

Spring Data will not populate the embedded Nodes when you return an Ad node. You need a dedicated result for this. Also, in your query you return the has_advertiser and is_favourite_to relations which have no mapping in the Ad class. They're completely irrelevant. Change your query to

@Query("MATCH (advertiser:User)<-[:HAS_ADVERTISER]-(a:Ad)-[:IS_FAVOURITE_TO]->(favourite:User) 
RETURN a AS ad, advertiser, collect(DISTINCT favourite) AS favourites")
public List<AdWithAdvertiserAndFavouritesResult getAdsWithAdvertisersAndFavourites()

The respective result is then

@QueryResult
public class AdWithAdvertiserAndFavouritesResult {
    private Ad ad;
    private User advertiser;
    private List<User> favourites;

    //only getters, no setters
}

You may want to consider defining an additional property for Ad that is unique and can be used as an index later since the query as it is will not scale and induce heavy load: You always match the entire set of Ad nodes. I strongly recommend not to do so.

Christoph Möbius
  • 1,352
  • 1
  • 12
  • 18
  • Hi Cristoph. Thanks for answering but I'm not sure if that's the point. I believe I could get the results here without a `@QueryResult`. See @Luanne answer...Anyway, I still cannot get it working, so I'll give it a try – troig Oct 24 '16 at 16:22