[Spring Data Neo4j] Duplicated relationship when two nodes have same Ids, but with differents Labels and classes

Summary

Using

Also tried using

I am facing an issue with duplicated relationships when same business Id is used on two entities of differents classes.

Steps to reproduce

  1. Setup a project with Spring Data Neo4j, and a Neo4j database
  2. Create two entities
@Node("Pol")
@Data
@With
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PolEntity {
    @Id
    private String code;

    @Relationship(type = RelationshipsNames.ROUTES)
    private List<RouteProperties> routes = new ArrayList<>();
}
@Node("Pod")
@Data
@With
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PodEntity {
    @Id
    private String code;
}

The RouteProperties class corresponds to the relationship between PolEntity and PolEntity, as following
(pol:Pol)-[r:ROUTES]->(pod:Pod)

@RelationshipProperties
@Data
@With
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RouteProperties {
    private Double truck;
    private String truckCurrency;

    private Double ft20;
    private String ft20Currency;

    private Double ft40;
    private String ft40Currency;

    private Double ft40HC;
    private String ft40HCCurrency;


    @TargetNode
    private PodEntity pod;
}
  1. Try to create the following PolEntity
new PolEntity()
                .withCode("TRMER")
                .withRoutes(List.of(
                        new RouteProperties()
                              .withPod(new PodEntity()
                                       .withCode("BEANR")
                               )
                              .withTruck(20),
                              // Other properties are optionals.
                         new RouteProperties()
                              .withPod(new PodEntity()
                                       .withCode("TRMER") // Here is the duplicated, but for another kind of node.
                               )
                              .withTruck(20)
                              // Other properties are optionals.
                ));

Actual results

If I understand well, using @Id with Spring Data Neo4j allows me to have the same Id on two entities, if they are not of the same class (ex Pol.code = 1 and Pod.code = 1).
And all is working when I am creating only nodes, without ROUTES relationships.

Using the debug, I can trace the requests performed by Spring Data Neo4j, and I see that the request is performed well on node creation

  • Pol creation (Id: TRMER)
    MERGE (n:`Pol` {code: $__id__}) SET n = $__properties__ RETURN id(n)" {__id__="TRMER", __properties__={createdAt: NULL, code: "TRMER", lastModifiedAt: NULL, createdBy: NULL, lastModifiedBy: NULL}}
  • Pod creation (Id: BEANR)
    MERGE (n:`Pod` {code: $__id__}) SET n = $__properties__ RETURN id(n)" {__id__="BEANR", __properties__={createdAt: NULL, code: "BEANR", lastModifiedAt: NULL, createdBy: NULL, lastModifiedBy: NULL}}
  • Pod creation with same Id as the Pol (Id: TRMER)
    MERGE (n:`Pod` {code: $__id__}) SET n = $__properties__ RETURN id(n)" {__id__="TRMER", __properties__={createdAt: NULL, code: "TRMER", lastModifiedAt: NULL, createdBy: NULL, lastModifiedBy: NULL}}

But during the relationship creation, the request performed by the client is not what I'm waiting for (here I only show one example, the one with the duplication issue)

  • MATCH (startNode) WHERE startNode.code = $fromId MATCH (endNode) WHERE id(endNode) = 2283 MERGE (startNode)-[relProps:ROUTES]->(endNode) SET relProps = $__properties__" {fromId="TRMER", __properties__={ft20Currency: "EUR", ft40Currency: "EUR", truck: NULL, truckCurrency: NULL, ft40: 236.0, ft20: 618.0, ft40HC: 818.0, ft40HCCurrency: "EUR"}}

As you see here the request starts by : MATCH (startNode) WHERE startNode.code = $fromId.

Here is the problem, the generated request does not check the type of startNode, and so if I have a Poland a Pod with the same Id, there are two matches.
For the example, when $fromId = TRMER, the requests match Pol with TMRER code and Pod with TMRER code. It should only match the Pol (and surprisingly, the endNode match uses id function of cypher, using the unique auto generated id... so no problem for endNode, only for startNode)

As a consequence, in the graph, with this previous example, I have this structure

  • One Pol with TRMER code
  • Two Pod with TRMER and BEANR code
  • Two relationships
    • (pol:Pol {code: 'TRMER')-[r:ROUTES]->(pod:Pod {code: 'BEANR'})
    • (pol:Pol {code: 'TRMER')-[r:ROUTES]->(pod:Pod {code: 'TRMER'})
  • And finally, a relationship I don't wan't, created because of the bug I'm describing
    • (pod:Pod {code: 'TRMER')-[r:ROUTES]->(pod:Pod {code: 'BEANR'})

As a workaround, I can add a generated Id matching the auto generated Id in neo4j on those entities, to make them unique.

But I want to know if I'am doing something wrong?
Maybe I need to add some annotations?
Or maybe it is a bug in Spring Data Neo4j?

I think this MATCH (startNode) WHERE startNode.code = $fromId MATCH (endNode) WHERE id(endNode) = 2283
should be MATCH (startNode:Pol) WHERE startNode.code = $fromId MATCH (endNode:Pod) WHERE id(endNode) = 2283

Thanks for reporting this, I will have a look.
I created Link relationships by label and id if a non-generated id is used. · Issue #2108 · spring-projects/spring-data-neo4j · GitHub for this issue to keep track of it.
If you have the feeling that you encounter issues or feature limitations, it might be a better option to directly file an issue at GitHub - 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.

1 Like

..and it will be coming to your machine with the upcoming service releases today ;)

1 Like

Great ! :smiley:
It was very quick !
Thanks !