Summary
Using
- Spring Data Neo4j 6.0.2 (actually the last version)
- Spring Boot 2.4.1 (also the last version)
- Spring WebFlux
Also tried using
- Neo4J SDN/RX (The WebFlux Neo4j client for Spring data before officially merged in Spring Data)
- Spring Boot 2.3.6.RELEASE
- Spring WebFlux
I am facing an issue with duplicated relationships when same business Id is used on two entities of differents classes.
Steps to reproduce
- Setup a project with Spring Data Neo4j, and a Neo4j database
- 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;
}
- 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 Pol
and 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
withTRMER
code - Two
Pod
withTRMER
andBEANR
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