Thomas Ricaud
Thomas Ricaud

Reputation: 1

Issue with Spring Data Neo4j: Fetching Relationships in Both Directions Without Cyclic Dependencies

I’m working with Spring 3.3.2 and Spring Data Neo4j 7.3.2. I have a repository that looks like this:

@Query("MATCH p=allShortestPaths((rootNode:SoftwareComponent {softwareComponent:${"$"}softwareComponent , version:${"$"}version })-[:DEPENDS_ON_SOFTWARE_COMPONENT *1..15]->(dependentNode:SoftwareComponent)) " +
        "WHERE rootNode <> dependentNode " +
        "WITH rootNode AS __sn__, collect(distinct (relationships(p))) AS __sr__, collect(distinct (dependentNode)) AS __srn__ " +
        "RETURN __sn__, __sr__, __srn__")
fun findDependencyByVersion(
        @Param("softwareComponent") softwareComponent: String, @Param("version") version: String
): List<SoftwareComponent>

And I am using the following Node definition:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id")
@Node("SoftwareComponent")
class SoftwareComponent(
    @Property(name = "softwareComponent") var softwareComponent: String,
    @Property(name = "version") var version: String,
    @Relationship(type = "DEPENDS_ON_SOFTWARE_COMPONENT", direction = Relationship.Direction.OUTGOING)
    @JsonManagedReference
    var dependencies: HashSet<DependsOnSoftwareComponent> = HashSet(),
    @Id @GeneratedValue var id: Long? = null
)

I am now trying to create another repository to fetch the reverse dependencies (the "consumers") of my SoftwareComponent, i.e., the nodes that depend on my current node. Here is the query I am trying to use:

@Query("MATCH p=(rootNode:SoftwareComponent {softwareComponent: ${"$"}softwareComponent, version: ${"$"}version})<-[r:DEPENDS_ON_SOFTWARE_COMPONENT *0..6]-(s:SoftwareComponent) " +
        "WITH rootNode AS __sn__, collect (relationships(p)) AS __sr__ , collect (nodes(p)) AS __srn__ " +
        "RETURN __sn__, __sr__, __srn__")
fun findConsumerByVersion(
    @Param("softwareComponent") softwareComponent: String, @Param("version") version: String
): List<SoftwareComponent>

The issue I am facing is:

  1. If I add a consumers field with an INCOMING relationship to my SoftwareComponent class. I get the following error message when calling both repositories (findDependencyByVersion et findConsumerByVersion).

Caused by: org.springframework.data.mapping.MappingException: The node with id 2887 has a logical cyclic mapping dependency; its creation caused the creation of another node that has a reference to this.

  1. I’ve tried creating a second class to represent the SoftwareComponent as a "consumer" (e.g., ReverseSoftwareComponent) and override le field dependencies with an INCOMING relationship, but I get an error indicating that the primary label is duplicated.

So my question is: How can I configure Spring Data Neo4j to handle bidirectional relationships without creating cyclic dependencies or duplicating the primary label?

Upvotes: 0

Views: 49

Answers (1)

meistermeier
meistermeier

Reputation: 8262

There are two ways this could be meant:

I end up with cyclic dependencies that make the application impossible to load

You have a logical cycle in your domain (Added after comment)

Similar to the problem/answer when rendering the result with JSON at the bottom, your domain is defined as sth. like SoftwareComponent->SoftwareComponent->Software... but also contains constructor dependencies in a way similar to

public SoftwareComponent(Software software) {}
/...
public Sofware(SoftwareComponent component) {}

When SDN tries to map this, it will follow the data. And if it finds a cycle, it will also follow this and eventually end in the

Caused by: org.springframework.data.mapping.MappingException: The node with id 2887 has a logical cyclic mapping dependency;

In those cases, you have to break this cycle by either defining a setter or wither in one of those entities. (Examples can be found here: https://docs.spring.io/spring-data/neo4j/reference/object-mapping/sdc-object-mapping.html#mapping.fundamentals.property-population)

The only downside of this is, that you cannot make the property final anymore.

(Edit: previous assumptions and solutions)

The application cannot load because other operations with this repository are slow

As you have already experienced, defining the bidirectional dependency in the database could lead to unexpected performance drops because with a highly connected graph, SDN will fetch "everything" that is reachable.

For your situation, there is a projection feature in place, that allows you to define interfaces that only reflect a sub-set of the defined global model.

You end up in a stack overflow when rendering the result

This is due to the fact that Jackson will chase every reachable field and SoftwareComponent->SoftwareComponent->Software... is an infinite self-referencing loop. To break out of this, the mentioned answer I gave about the use of Jackson (thanks @cybersam for finding this link already) is still valid. To free

Upvotes: 1

Related Questions