Skip to content

Commit 6fbecde

Browse files
committed
GH-2819 - Improve parent type detection in result record.
Closes #2819
1 parent cbc7ffa commit 6fbecde

File tree

4 files changed

+195
-20
lines changed

4 files changed

+195
-20
lines changed

src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java

+19-20
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
327327

328328
knownObjects.removeFromInCreation(internalId);
329329

330-
populateProperties(queryResult, nodeDescription, internalId, instance, lastMappedEntity, relationshipsFromResult, nodesFromResult, false);
330+
populateProperties(queryResult, (Neo4jPersistentEntity<ET>) genericTargetNodeDescription, nodeDescription, internalId, instance, lastMappedEntity, relationshipsFromResult, nodesFromResult, false);
331331

332332
PersistentPropertyAccessor<ET> propertyAccessor = concreteNodeDescription.getPropertyAccessor(instance);
333333
ET bean = propertyAccessor.getBean();
@@ -352,19 +352,19 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
352352
// AND (!!!)
353353
// 2. mutable target types
354354
// because we cannot just create new instances
355-
populateProperties(queryResult, nodeDescription, internalId, mappedObject, lastMappedEntity, relationshipsFromResult, nodesFromResult, true);
355+
populateProperties(queryResult, (Neo4jPersistentEntity<ET>) genericTargetNodeDescription, nodeDescription, internalId, mappedObject, lastMappedEntity, relationshipsFromResult, nodesFromResult, true);
356356
}
357357
return mappedObject;
358358
}
359359

360360

361-
private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescription, String internalId,
361+
private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEntity<ET> baseNodeDescription, Neo4jPersistentEntity<ET> moreConcreteNodeDescription, String internalId,
362362
ET mappedObject, @Nullable Object lastMappedEntity,
363363
Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult, boolean objectAlreadyMapped) {
364364

365-
List<String> allLabels = getLabels(queryResult, nodeDescription);
365+
List<String> allLabels = getLabels(queryResult, moreConcreteNodeDescription);
366366
NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore
367-
.deriveConcreteNodeDescription(nodeDescription, allLabels);
367+
.deriveConcreteNodeDescription(moreConcreteNodeDescription, allLabels);
368368

369369
@SuppressWarnings("unchecked")
370370
Neo4jPersistentEntity<ET> concreteNodeDescription = (Neo4jPersistentEntity<ET>) nodeDescriptionAndLabels
@@ -392,7 +392,7 @@ private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEnt
392392
knownObjects.storeObject(internalId, mappedObject);
393393

394394
AssociationHandlerSupport.of(concreteNodeDescription).doWithAssociations(
395-
populateFrom(queryResult, nodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult));
395+
populateFrom(queryResult, baseNodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult));
396396
}
397397

398398
@NonNull
@@ -464,12 +464,12 @@ public <T> T getParameterValue(Parameter<T, Neo4jPersistentProperty> parameter)
464464
// If we cannot find any value it does not mean that there isn't any.
465465
// The result set might contain associations not named CONCRETE_TYPE_TARGET but ABSTRACT_TYPE_TARGET.
466466
// For this we bubble up the hierarchy of NodeDescriptions.
467-
result = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, nodeDescription, genericNodeDescription, relationshipsFromResult, nodesFromResult)
467+
result = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, genericNodeDescription, relationshipsFromResult, nodesFromResult)
468468
.orElseGet(() -> {
469469
NodeDescription<?> parentNodeDescription = nodeDescription.getParentNodeDescription();
470470
T resultValue = null;
471471
while (parentNodeDescription != null) {
472-
Optional<Object> value = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, parentNodeDescription, parentNodeDescription, relationshipsFromResult, nodesFromResult);
472+
Optional<Object> value = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, parentNodeDescription, relationshipsFromResult, nodesFromResult);
473473
if (value.isPresent()) {
474474
resultValue = (T) value.get();
475475
break;
@@ -562,7 +562,7 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
562562
&& propertyValueNotNull;
563563

564564
if (populatedCollection) {
565-
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, baseDescription, relationshipsFromResult, nodesFromResult, false)
565+
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult, false)
566566
.ifPresent(value -> {
567567
Collection<?> providedCollection = (Collection<?>) value;
568568
Collection<?> existingValue = (Collection<?>) propertyValue;
@@ -585,7 +585,7 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
585585
return;
586586
}
587587

588-
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, baseDescription, relationshipsFromResult, nodesFromResult)
588+
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult)
589589
.ifPresent(value -> propertyAccessor.setProperty(persistentProperty, value));
590590
};
591591
}
@@ -609,13 +609,13 @@ private void mergeCollections(RelationshipDescription relationshipDescription, C
609609
}
610610

611611
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
612-
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, NodeDescription<?> genericNodeDescription, Collection<Relationship> relationshipsFromResult,
612+
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, Collection<Relationship> relationshipsFromResult,
613613
Collection<Node> nodesFromResult) {
614-
return createInstanceOfRelationships(persistentProperty, values, relationshipDescription, baseDescription, genericNodeDescription, relationshipsFromResult, nodesFromResult, true);
614+
return createInstanceOfRelationships(persistentProperty, values, relationshipDescription, baseDescription, relationshipsFromResult, nodesFromResult, true);
615615
}
616616

617617
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
618-
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, NodeDescription<?> genericNodeDescription, Collection<Relationship> relationshipsFromResult,
618+
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, Collection<Relationship> relationshipsFromResult,
619619
Collection<Node> nodesFromResult, boolean fetchMore) {
620620

621621
String typeOfRelationship = relationshipDescription.getType();
@@ -649,8 +649,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
649649
mappedObjectHandler = (type, mappedObject) -> value.add(mappedObject);
650650
}
651651

652-
String collectionName = relationshipDescription.generateRelatedNodesCollectionName(genericNodeDescription);
653-
652+
String collectionName = relationshipDescription.generateRelatedNodesCollectionName(baseDescription);
654653
Value list = values.get(collectionName);
655654

656655
List<Object> relationshipsAndProperties = new ArrayList<>();
@@ -697,12 +696,12 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
697696
if (fetchMore) {
698697
mappedObject = sourceNodeId != null && sourceNodeId.equals(targetNodeId)
699698
? knownObjects.getObject("N" + sourceNodeId)
700-
: map(possibleValueNode, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
699+
: map(possibleValueNode, concreteTargetNodeDescription, baseDescription, null, null, relationshipsFromResult, nodesFromResult);
701700
} else {
702701
Object objectFromStore = knownObjects.getObject("N" + targetNodeId);
703702
mappedObject = objectFromStore != null
704703
? objectFromStore
705-
: map(possibleValueNode, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
704+
: map(possibleValueNode, concreteTargetNodeDescription, baseDescription, null, null, relationshipsFromResult, nodesFromResult);
706705
}
707706

708707
if (relationshipDescription.hasRelationshipProperties()) {
@@ -734,16 +733,16 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
734733

735734
Object valueEntry;
736735
if (fetchMore) {
737-
valueEntry = map(relatedEntity, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
736+
valueEntry = map(relatedEntity, concreteTargetNodeDescription, genericTargetNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
738737
} else {
739738
Object objectFromStore = knownObjects.getObject(IdentitySupport.getInternalId(relatedEntity, null));
740739
valueEntry = objectFromStore != null
741740
? objectFromStore
742-
: map(relatedEntity, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
741+
: map(relatedEntity, concreteTargetNodeDescription, genericTargetNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
743742
}
744743

745744
if (relationshipDescription.hasRelationshipProperties()) {
746-
String sourceLabel = relationshipDescription.getSource().getMostAbstractParentLabel(genericNodeDescription);
745+
String sourceLabel = relationshipDescription.getSource().getMostAbstractParentLabel(baseDescription);
747746
String relationshipSymbolicName = sourceLabel
748747
+ RelationshipDescription.NAME_OF_RELATIONSHIP + targetLabel;
749748
Relationship relatedEntityRelationship = relatedEntity.get(relationshipSymbolicName)

src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java

+21
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@
142142
import org.springframework.data.neo4j.integration.issues.gh2639.LanguageRelationship;
143143
import org.springframework.data.neo4j.integration.issues.gh2639.ProgrammingLanguage;
144144
import org.springframework.data.neo4j.integration.issues.gh2639.Sales;
145+
import org.springframework.data.neo4j.integration.issues.gh2819.GH2819Model;
146+
import org.springframework.data.neo4j.integration.issues.gh2819.GH2819Repository;
145147
import org.springframework.data.neo4j.integration.issues.qbe.A;
146148
import org.springframework.data.neo4j.integration.issues.qbe.ARepository;
147149
import org.springframework.data.neo4j.integration.issues.qbe.B;
@@ -1030,6 +1032,25 @@ void throwCyclicMappingDependencyExceptionOnSelfReference(@Autowired GH2622Repos
10301032

10311033
}
10321034

1035+
@Test
1036+
@Tag("GH-2819")
1037+
void inheritanceAndProjectionShouldMapRelatedNodesCorrectly(@Autowired GH2819Repository repository, @Autowired Driver driver) {
1038+
try (var session = driver.session()) {
1039+
session.run("CREATE (a:ParentA:ChildA{name:'parentA', id:'a'})-[:HasBs]->(b:ParentB:ChildB{name:'parentB', id:'b'})-[:HasCs]->(c:ParentC:ChildC{name:'parentC', id:'c'})").consume();
1040+
}
1041+
1042+
var childAProjection = repository.findById("a", GH2819Model.ChildAProjection.class);
1043+
1044+
assertThat(childAProjection.getName()).isEqualTo("parentA");
1045+
var parentB = childAProjection.getParentB();
1046+
assertThat(parentB).isNotNull();
1047+
assertThat(parentB.getName()).isEqualTo("parentB");
1048+
var parentC = parentB.getParentC();
1049+
assertThat(parentC).isNotNull();
1050+
assertThat(parentC.getName()).isEqualTo("parentC");
1051+
1052+
}
1053+
10331054
@Configuration
10341055
@EnableTransactionManagement
10351056
@EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2011-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.issues.gh2819;
17+
18+
import org.springframework.data.neo4j.core.schema.Id;
19+
import org.springframework.data.neo4j.core.schema.Node;
20+
import org.springframework.data.neo4j.core.schema.Relationship;
21+
22+
/**
23+
* @author Gerrit Meier
24+
*/
25+
public class GH2819Model {
26+
27+
/**
28+
* Projection of ParentA/ChildA
29+
*/
30+
public interface ChildAProjection {
31+
String getName();
32+
GH2819Model.ChildBProjection getParentB();
33+
}
34+
35+
/**
36+
* Projection of ParentB/ChildB
37+
*/
38+
public interface ChildBProjection {
39+
String getName();
40+
GH2819Model.ChildCProjection getParentC();
41+
}
42+
43+
/**
44+
* Projection of ParentC/ChildC
45+
*/
46+
public interface ChildCProjection {
47+
String getName();
48+
}
49+
50+
/**
51+
* ParentA
52+
*/
53+
@Node
54+
public static class ParentA {
55+
@Id public String id;
56+
57+
@Relationship(type = "HasBs", direction = Relationship.Direction.OUTGOING)
58+
public ParentB parentB;
59+
60+
public String name;
61+
62+
public String getName() {
63+
return name;
64+
}
65+
66+
public ParentB getParentB() {
67+
return parentB;
68+
}
69+
}
70+
71+
/**
72+
* ParentB
73+
*/
74+
@Node
75+
public static class ParentB {
76+
@Id public String id;
77+
78+
@Relationship(type = "HasCs", direction = Relationship.Direction.OUTGOING)
79+
public ParentC parentC;
80+
81+
public String name;
82+
83+
public ParentC getParentC() {
84+
return parentC;
85+
}
86+
87+
public String getName() {
88+
return name;
89+
}
90+
}
91+
92+
/**
93+
* ParentC
94+
*/
95+
@Node
96+
public static class ParentC {
97+
@Id public String id;
98+
99+
public String name;
100+
101+
public String getName() {
102+
return name;
103+
}
104+
}
105+
106+
/**
107+
* ChildA
108+
*/
109+
@Node
110+
public static class ChildA extends ParentA {
111+
112+
}
113+
114+
/**
115+
* ChildB
116+
*/
117+
@Node
118+
public static class ChildB extends ParentB {
119+
120+
}
121+
122+
/**
123+
* ChildC
124+
*/
125+
@Node
126+
public static class ChildC extends ParentC {
127+
128+
}
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2011-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.issues.gh2819;
17+
18+
import org.springframework.data.neo4j.repository.Neo4jRepository;
19+
20+
/**
21+
* @author Gerrit Meier
22+
*/
23+
public interface GH2819Repository extends Neo4jRepository<GH2819Model.ChildA, String> {
24+
25+
GH2819Model.ChildAProjection findById(String id, Class<?> projectionClass);
26+
}

0 commit comments

Comments
 (0)