Skip to content

Commit b8bebe6

Browse files
committed
GH-2819 - Improve parent type detection in result record.
Closes #2819
1 parent d2e67bc commit b8bebe6

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
@@ -326,7 +326,7 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
326326

327327
knownObjects.removeFromInCreation(internalId);
328328

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

331331
PersistentPropertyAccessor<ET> propertyAccessor = concreteNodeDescription.getPropertyAccessor(getMostCurrentInstance(internalId, instance));
332332
ET bean = propertyAccessor.getBean();
@@ -351,7 +351,7 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
351351
// AND (!!!)
352352
// 2. mutable target types
353353
// because we cannot just create new instances
354-
populateProperties(queryResult, nodeDescription, internalId, mappedObject, lastMappedEntity, relationshipsFromResult, nodesFromResult, true);
354+
populateProperties(queryResult, (Neo4jPersistentEntity<ET>) genericTargetNodeDescription, nodeDescription, internalId, mappedObject, lastMappedEntity, relationshipsFromResult, nodesFromResult, true);
355355
}
356356
// due to a needed side effect in `populateProperties`, the entity might have been changed
357357
return getMostCurrentInstance(internalId, mappedObject);
@@ -363,13 +363,13 @@ private <ET> ET getMostCurrentInstance(String internalId, ET fallbackInstance) {
363363
}
364364

365365

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

370-
List<String> allLabels = getLabels(queryResult, nodeDescription);
370+
List<String> allLabels = getLabels(queryResult, moreConcreteNodeDescription);
371371
NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore
372-
.deriveConcreteNodeDescription(nodeDescription, allLabels);
372+
.deriveConcreteNodeDescription(moreConcreteNodeDescription, allLabels);
373373

374374
@SuppressWarnings("unchecked")
375375
Neo4jPersistentEntity<ET> concreteNodeDescription = (Neo4jPersistentEntity<ET>) nodeDescriptionAndLabels
@@ -397,7 +397,7 @@ private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEnt
397397
knownObjects.storeObject(internalId, propertyAccessor.getBean());
398398

399399
AssociationHandlerSupport.of(concreteNodeDescription).doWithAssociations(
400-
populateFrom(queryResult, nodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult));
400+
populateFrom(queryResult, baseNodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult));
401401
}
402402

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

569569
if (populatedCollection) {
570-
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, baseDescription, relationshipsFromResult, nodesFromResult, false)
570+
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult, false)
571571
.ifPresent(value -> {
572572
Collection<?> providedCollection = (Collection<?>) value;
573573
Collection<?> existingValue = (Collection<?>) propertyValue;
@@ -590,7 +590,7 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
590590
return;
591591
}
592592

593-
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, baseDescription, relationshipsFromResult, nodesFromResult)
593+
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult)
594594
.ifPresent(value -> propertyAccessor.setProperty(persistentProperty, value));
595595
};
596596
}
@@ -614,13 +614,13 @@ private void mergeCollections(RelationshipDescription relationshipDescription, C
614614
}
615615

616616
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
617-
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, NodeDescription<?> genericNodeDescription, Collection<Relationship> relationshipsFromResult,
617+
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, Collection<Relationship> relationshipsFromResult,
618618
Collection<Node> nodesFromResult) {
619-
return createInstanceOfRelationships(persistentProperty, values, relationshipDescription, baseDescription, genericNodeDescription, relationshipsFromResult, nodesFromResult, true);
619+
return createInstanceOfRelationships(persistentProperty, values, relationshipDescription, baseDescription, relationshipsFromResult, nodesFromResult, true);
620620
}
621621

622622
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
623-
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, NodeDescription<?> genericNodeDescription, Collection<Relationship> relationshipsFromResult,
623+
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, Collection<Relationship> relationshipsFromResult,
624624
Collection<Node> nodesFromResult, boolean fetchMore) {
625625

626626
String typeOfRelationship = relationshipDescription.getType();
@@ -654,8 +654,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
654654
mappedObjectHandler = (type, mappedObject) -> value.add(mappedObject);
655655
}
656656

657-
String collectionName = relationshipDescription.generateRelatedNodesCollectionName(genericNodeDescription);
658-
657+
String collectionName = relationshipDescription.generateRelatedNodesCollectionName(baseDescription);
659658
Value list = values.get(collectionName);
660659
boolean relationshipListEmptyOrNull = Values.NULL.equals(list);
661660

@@ -715,12 +714,12 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
715714
if (fetchMore) {
716715
mappedObject = sourceNodeId != null && sourceNodeId.equals(targetNodeId)
717716
? knownObjects.getObject("N" + sourceNodeId)
718-
: map(possibleValueNode, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
717+
: map(possibleValueNode, concreteTargetNodeDescription, baseDescription, null, null, relationshipsFromResult, nodesFromResult);
719718
} else {
720719
Object objectFromStore = knownObjects.getObject("N" + targetNodeId);
721720
mappedObject = objectFromStore != null
722721
? objectFromStore
723-
: map(possibleValueNode, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
722+
: map(possibleValueNode, concreteTargetNodeDescription, baseDescription, null, null, relationshipsFromResult, nodesFromResult);
724723
}
725724

726725
if (relationshipDescription.hasRelationshipProperties()) {
@@ -752,16 +751,16 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
752751

753752
Object valueEntry;
754753
if (fetchMore) {
755-
valueEntry = map(relatedEntity, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
754+
valueEntry = map(relatedEntity, concreteTargetNodeDescription, genericTargetNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
756755
} else {
757756
Object objectFromStore = knownObjects.getObject(IdentitySupport.getPrefixedElementId(relatedEntity, null));
758757
valueEntry = objectFromStore != null
759758
? objectFromStore
760-
: map(relatedEntity, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
759+
: map(relatedEntity, concreteTargetNodeDescription, genericTargetNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
761760
}
762761

763762
if (relationshipDescription.hasRelationshipProperties()) {
764-
String sourceLabel = relationshipDescription.getSource().getMostAbstractParentLabel(genericNodeDescription);
763+
String sourceLabel = relationshipDescription.getSource().getMostAbstractParentLabel(baseDescription);
765764
String relationshipSymbolicName = sourceLabel
766765
+ RelationshipDescription.NAME_OF_RELATIONSHIP + targetLabel;
767766
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
@@ -143,6 +143,8 @@
143143
import org.springframework.data.neo4j.integration.issues.gh2639.LanguageRelationship;
144144
import org.springframework.data.neo4j.integration.issues.gh2639.ProgrammingLanguage;
145145
import org.springframework.data.neo4j.integration.issues.gh2639.Sales;
146+
import org.springframework.data.neo4j.integration.issues.gh2819.GH2819Model;
147+
import org.springframework.data.neo4j.integration.issues.gh2819.GH2819Repository;
146148
import org.springframework.data.neo4j.integration.issues.qbe.A;
147149
import org.springframework.data.neo4j.integration.issues.qbe.ARepository;
148150
import org.springframework.data.neo4j.integration.issues.qbe.B;
@@ -1103,6 +1105,25 @@ void mapsProjectionChainWithRelationshipProperties(@Autowired FirstLevelEntityRe
11031105
});
11041106
}
11051107

1108+
@Test
1109+
@Tag("GH-2819")
1110+
void inheritanceAndProjectionShouldMapRelatedNodesCorrectly(@Autowired GH2819Repository repository, @Autowired Driver driver) {
1111+
try (var session = driver.session()) {
1112+
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();
1113+
}
1114+
1115+
var childAProjection = repository.findById("a", GH2819Model.ChildAProjection.class);
1116+
1117+
assertThat(childAProjection.getName()).isEqualTo("parentA");
1118+
var parentB = childAProjection.getParentB();
1119+
assertThat(parentB).isNotNull();
1120+
assertThat(parentB.getName()).isEqualTo("parentB");
1121+
var parentC = parentB.getParentC();
1122+
assertThat(parentC).isNotNull();
1123+
assertThat(parentC.getName()).isEqualTo("parentC");
1124+
1125+
}
1126+
11061127
@Configuration
11071128
@EnableTransactionManagement
11081129
@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)