spring-data-neo4j
spring-data-neo4j copied to clipboard
Multiple request for projection
Given
@Data
@Setter(AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@SuperBuilder(toBuilder = true)
abstract public class BaseNodeEntity {
@Id
@GeneratedValue(UUIDStringGenerator.class)
@EqualsAndHashCode.Include
private String nodeId;
@Relationship(type = "CHILD_OF", direction = OUTGOING)
private NodeEntity parent;
@Relationship(type = "HAS_TYPE", direction = OUTGOING)
private NodeType nodeType;
}
@Node
@Data
@Setter(AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
@SuperBuilder(toBuilder = true)
public class MeasurementMeta extends BaseNodeEntity {
@Relationship(type = "WEIGHTS", direction = OUTGOING)
private MeasurementMeta baseMeasurement;
}
@Node
@Data
@Setter(AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
@SuperBuilder(toBuilder = true)
public class NodeEntity extends BaseNodeEntity {
}
@Node
@Value
@AllArgsConstructor
@EqualsAndHashCode
@Immutable
public class NodeType {
@Id
String nodeTypeId;
}
public interface BaseNodeFieldsProjection {
String getNodeId();
}
public interface ApiMeasurementMetaProjection extends BaseNodeFieldsProjection {
BaseNodeFieldsProjection getParent();
NodeType getNodeType();
BaseNodeFieldsProjection getBaseMeasurement();
}
interface MeasurementMetaRepository extends Repository<MeasurementMeta, String> {
<R> Optional<R> findByNodeId(String nodeId, Class<R> clazz);
}
CREATE (m1:MeasurementMeta{nodeId: 'm1'})
CREATE (m2:MeasurementMeta{nodeId: 'm2'})
CREATE (nt2:NodeType{nodeTypeId: 'nt2'})
CREATE (m1)-[:HAS_TYPE]->(nt2)
CREATE (m2)-[:HAS_TYPE]->(nt2)
CREATE (m1)-[:WEIGHTS]->(m2)
The following invocation:
repository.findByNodeId("m1", ApiMeasurementMetaProjection.class);
Will run multiple queries against the db:
[main] 2021-11-09 18:20:32,864 DEBUG org.springframework.data.neo4j.cypher: 313 - Executing:
MATCH (measurementMeta:`MeasurementMeta`) WHERE measurementMeta.nodeId = $nodeId WITH collect(id(measurementMeta)) AS __sn__ RETURN __sn__
[main] 2021-11-09 18:20:32,864 TRACE org.springframework.data.neo4j.cypher: 334 - with parameters:
:params {nodeId: "m1"}
[main] 2021-11-09 18:20:32,969 DEBUG org.springframework.data.neo4j.cypher: 313 - Executing:
MATCH (measurementMeta:`MeasurementMeta`) WHERE measurementMeta.nodeId = $nodeId OPTIONAL MATCH (measurementMeta)-[__sr__:`HAS_TYPE`]->(__srn__:`NodeType`) WITH collect(id(measurementMeta)) AS __sn__, collect(id(__srn__)) AS __srn__, collect(id(__sr__)) AS __sr__ RETURN __sn__, __srn__, __sr__
[main] 2021-11-09 18:20:32,969 TRACE org.springframework.data.neo4j.cypher: 334 - with parameters:
:params {nodeId: "m1"}
[main] 2021-11-09 18:20:33,094 DEBUG org.springframework.data.neo4j.cypher: 313 - Executing:
MATCH (measurementMeta:`MeasurementMeta`) WHERE measurementMeta.nodeId = $nodeId OPTIONAL MATCH (measurementMeta)-[__sr__:`CHILD_OF`]->(__srn__:`NodeEntity`) WITH collect(id(measurementMeta)) AS __sn__, collect(id(__srn__)) AS __srn__, collect(id(__sr__)) AS __sr__ RETURN __sn__, __srn__, __sr__
[main] 2021-11-09 18:20:33,094 TRACE org.springframework.data.neo4j.cypher: 334 - with parameters:
:params {nodeId: "m1"}
[main] 2021-11-09 18:20:33,147 DEBUG org.springframework.data.neo4j.cypher: 313 - Executing:
MATCH (measurementMeta:`MeasurementMeta`) WHERE measurementMeta.nodeId = $nodeId OPTIONAL MATCH (measurementMeta)-[__sr__:`WEIGHTS`]->(__srn__:`MeasurementMeta`) WITH collect(id(measurementMeta)) AS __sn__, collect(id(__srn__)) AS __srn__, collect(id(__sr__)) AS __sr__ RETURN __sn__, __srn__, __sr__
[main] 2021-11-09 18:20:33,147 TRACE org.springframework.data.neo4j.cypher: 334 - with parameters:
:params {nodeId: "m1"}
[main] 2021-11-09 18:20:33,196 DEBUG org.springframework.data.neo4j.cypher: 313 - Executing:
MATCH (rootNodeIds) WHERE id(rootNodeIds) IN $rootNodeIds WITH collect(rootNodeIds) AS n OPTIONAL MATCH ()-[relationshipIds]-() WHERE id(relationshipIds) IN $relationshipIds WITH n, collect(DISTINCT relationshipIds) AS __sr__ OPTIONAL MATCH (relatedNodeIds) WHERE id(relatedNodeIds) IN $relatedNodeIds WITH n, __sr__ AS __sr__, collect(DISTINCT relatedNodeIds) AS __srn__ UNWIND n AS rootNodeIds WITH rootNodeIds AS measurementMeta, __sr__, __srn__ RETURN measurementMeta AS __sn__, __sr__, __srn__
[main] 2021-11-09 18:20:33,196 TRACE org.springframework.data.neo4j.cypher: 334 - with parameters:
:params {rootNodeIds: [22], relatedNodeIds: [23, 24], relationshipIds: [0, 2]}
In my opinion it is not necessary to query all relations separately for the given projection. One query should be sufficient here.
As soon as I remove ApiMeasurementMetaProjection::getBaseMeasurement()
from the projection, the data is queried by a single call:
MATCH (measurementMeta:`MeasurementMeta`)
WHERE measurementMeta.nodeId = $nodeId
RETURN measurementMeta{
.nodeId,
__nodeLabels__:labels(measurementMeta),
__internalNeo4jId__:id(measurementMeta),
MeasurementMeta_CHILD_OF_NodeEntity:[(measurementMeta)-[:`CHILD_OF`]->(measurementMeta_parent:`NodeEntity`) | measurementMeta_parent{
.nodeId,
__nodeLabels__:labels(measurementMeta_parent),
__internalNeo4jId__:id(measurementMeta_parent),
NodeEntity_HAS_TYPE_NodeType:[(measurementMeta_parent)-[:`HAS_TYPE`]->(measurementMeta_parent_nodeType:`NodeType`) | measurementMeta_parent_nodeType{
.nodeTypeId,
__nodeLabels__:labels(measurementMeta_parent_nodeType),
__internalNeo4jId__:id(measurementMeta_parent_nodeType)}]
}],
MeasurementMeta_HAS_TYPE_NodeType:[(measurementMeta)-[:`HAS_TYPE`]->(measurementMeta_nodeType:`NodeType`) | measurementMeta_nodeType{
.nodeTypeId,
__nodeLabels__:labels(measurementMeta_nodeType),
__internalNeo4jId__:id(measurementMeta_nodeType)
}]
}
I leave the card as a long-term enhancement open. At the moment it is not possible to determine at the moment of the circle detection if we are inspecting a projection or entity. In the entity-world SDN only sees this:
public class MeasurementMeta extends BaseNodeEntity {
@Relationship(type = "WEIGHTS", direction = OUTGOING)
private MeasurementMeta baseMeasurement;
}
and interprets it as a same-type relationship -what it is- and falls back to the broader data-driver discovery with cascading queries.
I know that this ticket is old and a few things happened during this: The multilevel projection much improved and for the case above ^^ should be solved, .... but this leaves
public interface ApiMeasurementMetaProjection extends BaseNodeFieldsProjection {
BaseNodeFieldsProjection getParent();
//....
}
as a cycle because it could be the very same. Good news are that there is now #2840 created which hopefully allows users to specify that the modelled domain cycle should not be seen as a data cycle when querying.
Closing this issue now because I cannot add anything further here.