Hello,
I'm using the neo4jClient to execute a custom query, and I'm returning a custom projection class with entities inside.
I need my entities to be hydrated with the data returned from the cypher query.
The issue is that relations are not hydrated in my returned entity.
Version used: SDN 6.1.5
Here is my sample neo4j model:
- Any node with label ObjetMaquette has a relation EST_DE_TYPE to NoeudType.
- Any node with label ObjetMaquette has a relation A_POUR_ENFANT to another ObjetMaquette (parent->child relation)
- Any node with label Formation is also an ObjetMaquette (inheritance).
My custom query will return a list of ObjetMaquette with their root Formations (if they have)
The returned ObjetMaquette needs to be hydrated with the EST_DE_TYPE relation.
The issue is that they are not hydrated.
Am I missing something ?
Thanks !
Edit: I did more tests
- neo4jClient and returning a projection class: relations are not hydrated
- neo4jTemplate and returning an entity: relations are correctly hydrated
- neo4jTemplate and returning a projection class: NoRootNodeMappingException: Could not find mappable nodes or relationships inside Record<{om: node<0>, formationsParentes: [node<2>], types: [node<3>], rs: [relationship<0>]}> for org.springframework.data.neo4j.core.mapping.DefaultNeo4jPersistentEntity@38c10190
- Query method (@Query) and returning a projection class: relations are not hydrated
- Query method returning a projection interface (same structure as the projection class): NotReadablePropertyException: Invalid property 'om' of bean class [com.example.sdn6.entity.NoeudMaquetteEntity]: Could not find field for property during fallback access!
So, I'm still stuck :(
Link to github project: GitHub - gonzalad/sdn6-tests at custom-mapping-relations-not-hydrated, just run ./mvnw test to reproduce the issue (the test uses testcontainer)
The test code
@Test
void testFindAllNoeudAndFormationsParentesByCode() {
String code = "OF1";
List<NoeudAndFormationsParentesResult> noeudAndFormations = repository.findAllNoeudAndFormationsParentesByCode(code);
assertThat(noeudAndFormations).hasSize(1);
NoeudAndFormationsParentesResult result = noeudAndFormations.get(0);
assertThat(result.getOm()).isNotNull();
assertThat(result.getOm().getCode()).isEqualTo(code);
assertThat(result.getFormationsParentes()).hasSize(1);
// issue: relations are not hydrated
assertThat(result.getOm().getType()).isNotNull();
}
Projection class
public class NoeudAndFormationsParentesResult {
private final NoeudMaquetteEntity om;
private final List<FormationEntity> formationsParentes;
My custom query
@Override
public List<NoeudAndFormationsParentesResult> findAllNoeudAndFormationsParentesByCode(String code) {
var query = "";
query += " MATCH pof = (om:ObjetMaquette)-[r:EST_DE_TYPE]->(type:NoeudType)\n"
+ " WHERE \n"
+ " om.code = $code\n";
query += "WITH om, collect(type) as types, collect(r) as rs\n";
query += "RETURN \n";
query += " om as om,\n";
query += " [(om)<-[:A_POUR_ENFANT*1..]-(f:Formation) | f] as formationsParentes,\n";
query += " types, rs";
//@formatter:on
Map<String, Object> parameters = new HashMap<>();
parameters.put("code", code);
List<NoeudAndFormationsParentesResult> noeudsAndFormations = new ArrayList<>();
BiFunction<TypeSystem, MapAccessor, NoeudMaquetteEntity> mappingFunction = neo4jMappingContext.getRequiredMappingFunctionFor(NoeudMaquetteEntity.class);
neo4jClient.query(query) //
.bindAll(parameters) //
.fetchAs(NoeudMaquetteEntity.class) //
.mappedBy((t, r) -> {
NoeudMaquetteEntity om = mappingFunction.apply(t, r.get("om"));
List<FormationEntity> formationsParentes = this.asListNoeudMaquetteEntity(r.get("formationsParentes"), mappingFunction, t, FormationEntity.class);
noeudsAndFormations.add(new NoeudAndFormationsParentesResult(om, formationsParentes));
return om;
}) //
.all();
return noeudsAndFormations;
}
private <S extends NoeudMaquetteEntity> List<S> asListNoeudMaquetteEntity(Value value, //
BiFunction<TypeSystem, MapAccessor, NoeudMaquetteEntity> mappingFunction, //
TypeSystem t, //
Class<S> entityType) {
return StreamSupport.stream(value.values().spliterator(), false) //
.map(v -> mappingFunction.apply(t, v)) //
.map(entityType::cast) //
.collect(Collectors.toList());
}
The assertThat(result.getOm().getType()).isNotNull() fails
My test data
CREATE (of1:ObjetMaquette {code:'OF1', idDefinition: 'a4f901ab-0bdc-4fb6-a734-19dc44f99303'})
CREATE (of2:ObjetMaquette {code:'OF2', idDefinition: 'f57a0d06-b333-45ac-9239-442fb6f00cb3'})
CREATE (f1:ObjetMaquette:Formation {code:'F1', idDefinition: '7344b15d-0268-47b7-ab00-a81fea73859d'})
CREATE (tof:NoeudType {code:'PT'})
CREATE (tf:NoeudType {code:'Formation'})
CREATE
(of1)-[:EST_DE_TYPE]->(tof),
(f1)-[:A_POUR_ENFANT]->(of1)