spring-data-neo4j icon indicating copy to clipboard operation
spring-data-neo4j copied to clipboard

Multiple request for projection

Open Andy2003 opened this issue 3 years ago • 2 comments

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.

Andy2003 avatar Nov 09 '21 17:11 Andy2003

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)
    }]
}

Andy2003 avatar Nov 09 '21 17:11 Andy2003

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.

meistermeier avatar Nov 10 '21 16:11 meistermeier

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.

meistermeier avatar Dec 14 '23 09:12 meistermeier