spring-projects / spring-data-neo4j

Provide support to increase developer productivity in Java when using Neo4j. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
http://spring.io/projects/spring-data-neo4j
Apache License 2.0
833 stars 617 forks source link

Retrieve all polymorphic relationships with polymorphic retrieval with limited depth. #1849

Closed spring-projects-issues closed 3 years ago

spring-projects-issues commented 4 years ago

Graham Lea opened DATAGRAPH-1287 and commented

I'm having a problem with Spring Data for Neo4J and polymorphic retrieval. The retrieval of polymorphic nodes on the end of a common relationship type is working (I'm impressed!). However, further retrieval of nodes with polymorphic relationships is not working.

Here's some example data:

MERGE (p:Person {name:"Billy"})<-[:OWNED_BY]-(d:Dog {name: "Ralph", breed: "Muppet"})<-[:HOUSES]-(k:Kennel)
[MERGE (p:Person {name:"Sally"})<-[:LIVES_WITH]-(c:Cat {name: "Mittens", food: "Birds"})<-[:HOUSES]-(k:Kennel)

 

 

And here's the model in Kotlin:

 

class Kennel {
    @Id
    @GeneratedValue
    var id: Long? = null

    @Relationship(type = "HOUSES", direction = Relationship.OUTGOING)
    lateinit var pet: Pet
}

class Person(var name: String) {
    @Id
    @GeneratedValue
    var id: Long? = null

    override fun toString() = "Person(${name})"
}

abstract class Pet(var name: String){
    @Id
    @GeneratedValue
    var id: Long? = null

    abstract var person: Person?

    override fun toString() = "${this::class.simpleName}(${name})"
}

class Dog(name: String, val breed: String): Pet(name) {
    @Relationship(type = "OWNED_BY", direction = Relationship.OUTGOING)
    override var person: Person? = null
}

class Cat(name: String, val food: String): Pet(name) {
    @Relationship(type = "LIVES_WITH", direction = Relationship.OUTGOING)
    override var person: Person? = null
}

@Repository
interface KennelRepository : Neo4jRepository<Kennel, Long> {
}

 

Now, if I do:

 

val kennels = kennelRepository.findAll(999)
val pets = kennels.map { it.pet }
println("Pets: " + pets)

I'll get "Pets: [Dog(Ralph), Cat(Mittens)]" - all good.

 

but if I do:

 

val people = kennels.map { it.pet.person }
println("People: " + people)

then I get "People: [null, Person(Sally)]" - the person who the Dog is OWNED_BY is not retrieved.

I've switched on querying logging, which shows:

MATCH (n:`Kennel`) WITH n RETURN n,[ [ (n)-[r_h1:`HOUSES`]->(x1) | [ r_h1, x1, [ [ (x1)-[r_l2:`LIVES_WITH`]->(p2:`Person`) | [ r_l2, p2 ] ] ] ] ] ] with params {}

So it looks like the query generator is picking one type of relationship to follow (the "LIVES_WITH" in this case) and ignoring the other one. I checked that if both the Cat and the Dog have the same relationship type, then the Person is retrieved for both instances.

The query above returns the correct result if a multiple-match relationship is supplied instead:

[r_l2:`LIVES_WITH`|:`OWNED_BY`]

Also, it would be nice if it supported these multiple relationship types being in multiple directions. So, Person->OWNS->Dog (instead of OWNED_BY) would work with an INCOMING relationship on the model and the query would not specify a direction.

I know this seems a little contrived but it does match an actual scenario in my real model, which has nothing to do with kennels or pets.

 


Affects: 5.2.4 (Moore SR4)

spring-projects-issues commented 4 years ago

Graham Lea commented

FWIW, I tried dropping the "type" from the Relationship annotations, but this seemed to just drop that relationship from the query entirely:

MATCH (n:`Kennel`) WITH n RETURN n,[ [ (n)-[r_h1:`HOUSES`]->(x1) | [ r_h1, x1, [ [ (x1)-[r_p2:`PERSON`]->(p2:`Person`) | [ r_p2, p2 ] ] ] ] ] ] with params {}
spring-projects-issues commented 4 years ago

Graham Lea commented

Workaround!

As advised by Luanne Misquitta's blog (https://graphaware.com/neo4j/2016/04/06/mapping-query-entities-sdn.html), it's possible to workaround this by using a custom query that explicitly returns all of the desired nodes & relationships: 

@Query("MATCH (k:Kennel)-[r1:HOUSES]->(pet)-[r2:OWNED_BY|:LIVES_WITH]-(person:Person) RETURN k, r1, pet, r2, person")
fun findThem(): Iterable<Kennel>

This produces the desired result in the above test case:

People: [Person(Billy), Person(Sally)]

In a real-life graph, it may become tiresome to describe the whole depth of the graph desired to be returned, compared to the promise of SDN's NodeRepository.findAll(depth) functions.

 

michael-simons commented 3 years ago

Hey @GrahamLea. Thanks for the detailed report. Now that issue has some history after being transferred from JIRA to GitHub and finally be fixed in OGM 3.2.23.

You can either manually upgrade to OGM 3.2.23 in your project or wait until a patched SDN 5.3. release comes out, depending on the latest OGM version.