Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
TL;DR: @Depth(value = -1) throws nullpointer and other values above 1 are ignored
In my Spring Boot with Neo4j project I have 3 simple entities with relationships:
@NodeEntity
data class Metric(
@Id @GeneratedValue val id: Long = -1,
val name: String = "",
val description: String = "",
@Relationship(type = "CALCULATES")
val calculates: MutableSet<Calculable> = mutableSetOf()
fun calculates(calculable: Calculus) = calculates.add(calculable)
fun calculate() = calculates.map { c -> c.calculate() }.sum()
interface Calculable {
fun calculate(): Double
@NodeEntity
data class Calculus(
@Id @GeneratedValue val id: Long = -1,
val name: String = "",
@Relationship(type = "LEFT")
var left: Calculable? = null,
@Relationship(type = "RIGHT")
var right: Calculable? = null,
var operator: Operator? = null
) : Calculable {
override fun calculate(): Double =
operator!!.apply(left!!.calculate(), right!!.calculate())
@NodeEntity
data class Value(
@Id @GeneratedValue val id: Long = -1,
val name: String = "",
var value: Double = 0.0
) : Calculable {
override fun calculate(): Double = value
enum class Operator : BinaryOperator<Double>, DoubleBinaryOperator {//not relevant}
I create a simple graph like this one:
With the following repositories:
@Repository
interface MetricRepository : Neo4jRepository<Metric, Long>{
@Depth(value = 2)
fun findByName(name: String): Metric?
@Repository
interface CalculusRepository : Neo4jRepository<Calculus, Long>{
fun findByName(name: String): Calculus?
@Repository
interface ValueRepository : Neo4jRepository<Value, Long>{
fun findByName(name: String): Value?
And the following code:
// calculus
val five = valueRepository.save(Value(
name = "5",
value = 5.0
val two = valueRepository.save(Value(
name = "2",
value = 2.0
val fiveTimesTwo = calculusRepository.save(Calculus(
name = "5 * 2",
operator = Operator.TIMES,
left = five,
right = two
println("---")
println(fiveTimesTwo)
val fromRepository = calculusRepository.findByName("5 * 2")!!
println(fromRepository) // sometimes has different id than fiveTimesTwo
println("5 * 2 = ${fromRepository.calculate()}")
println("--- \n")
// metric
val metric = metricRepository.save(Metric(
name = "Metric 1",
description = "Measures a calculus",
calculates = mutableSetOf(fromRepository)
metricRepository.save(metric)
println("---")
println(metric)
val metricFromRepository = metricRepository.findByName("Metric 1")!!
println(metricFromRepository) // calculates node is partially empty
println("--- \n")
To retrieve the same graph as shown in the picture above (taken from the actual neo4j dashboard), I do metricRepository.findByName("Metric 1")
which has @Depth(value = 2)
and then print the saved metric and the retrieved metric:
Metric(id=9, name=Metric 1, description=Measures a calculus, calculates=[Calculus(id=2, name=5 * 2, left=Value(id=18, name=5, value=5.0), right=Value(id=1, name=2, value=2.0), operator=TIMES)])
Metric(id=9, name=Metric 1, description=Measures a calculus, calculates=[Calculus(id=2, name=5 * 2, left=null, right=null, operator=TIMES)])
No matter the value of the depth, I can't get the Metric node with all his children nodes, it retrieves one level deep max and returns null on the leaf nodes.
I've read in the docs that depth=-1 retrieves the fully-resolved node but doing so causes the findByName()
method to fail with a null pointer: kotlin.KotlinNullPointerException: null
Here is a list of resources I've consulted and a working GitHub repository with the full code:
GitHub Repo
Spring Data Neo4j Reference Documentation
Neo4j-OGM Docs
Final notes:
The entities all have default parameters because Kotlin then makes an empty constructor, I think the OGM needs it
I've also tried making custom queries but couldn't specify the depth value because there are different relationships and can be at different levels
To use the GitHub repository I linked you must have Neo4j installed, the repo has a stackoverflow-question branch with all the code.
Versions:
Spring boot: 2.3.0.BUILD-SNAPSHOT
spring-boot-starter-data-neo4j: 2.3.0.BUILD-SNAPSHOT
Thank you for helping and all feedback is welcomed!
The problem is not with the query depth but with the model. The Metric
entity has a relation with Calculable
, but Calculable
itself has no relationships defined. Spring Data cannot scan all possible implementations of the Calculable
interface for their relationships. If you changed Metrics.calculates
type to MutableSet<Calculus>
, it would work as expected.
To see Cypher requests send to the server you can add logging.level.org.neo4j.ogm.drivers.bolt=DEBUG
to the application.properties
Request before the change:
MATCH (n:`Metric`) WHERE n.`name` = $`name_0` WITH n RETURN n,[ [ (n)->[r_c1:`CALCULATES`]->(x1) | [ r_c1, x1 ] ] ], ID(n) with params {name_0=Metric 1}
Request after the change:
MATCH (n:`Metric`) WHERE n.`name` = $`name_0` WITH n RETURN n,[ [ (n)->[r_c1:`CALCULATES`]->(c1:`Calculus`) | [ r_c1, c1, [ [ (c1)-[r_l2:`LEFT`]-(v2:`Value`) | [ r_l2, v2 ] ], [ (c1)-[r_r2:`RIGHT`]-(v2:`Value`) | [ r_r2, v2 ] ] ] ] ] ], ID(n) with params {name_0=Metric 1}
–
–
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.