Skip to content

Commit b7236a3

Browse files
committed
GH-2452 - Improve mapping of constructor relationships.
Closes #2452
1 parent b2398db commit b7236a3

File tree

3 files changed

+86
-6
lines changed

3 files changed

+86
-6
lines changed

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

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,23 @@ public <T> T getParameterValue(PreferredConstructor.Parameter<T, Neo4jPersistent
429429
String propertyFieldName = matchingProperty.getFieldName();
430430
return r.getFieldName().equals(propertyFieldName);
431431
}).findFirst().get();
432-
result = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, relationshipsFromResult, nodesFromResult).orElse(null);
432+
// If we cannot find any value it does not mean that there isn't any.
433+
// The result set might contain associations not named CONCRETE_TYPE_TARGET but ABSTRACT_TYPE_TARGET.
434+
// For this we bubble up the hierarchy of NodeDescriptions.
435+
result = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, relationshipsFromResult, nodesFromResult, null)
436+
.orElseGet(() -> {
437+
NodeDescription<?> parentNodeDescription = nodeDescription.getParentNodeDescription();
438+
T resultValue = null;
439+
while (parentNodeDescription != null) {
440+
Optional<Object> value = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, relationshipsFromResult, nodesFromResult, parentNodeDescription);
441+
if (value.isPresent()) {
442+
resultValue = (T) value.get();
443+
break;
444+
}
445+
parentNodeDescription = parentNodeDescription.getParentNodeDescription();
446+
}
447+
return resultValue;
448+
});
433449
} else if (matchingProperty.isDynamicLabels()) {
434450
result = createDynamicLabelsProperty(matchingProperty.getTypeInformation(), surplusLabels);
435451
} else if (matchingProperty.isEntityWithRelationshipProperties()) {
@@ -518,13 +534,14 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
518534
}
519535
}
520536

521-
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, relationshipsFromResult, nodesFromResult)
537+
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, relationshipsFromResult, nodesFromResult, null)
522538
.ifPresent(value -> propertyAccessor.setProperty(persistentProperty, value));
523539
};
524540
}
525541

526542
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
527-
RelationshipDescription relationshipDescription, Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult) {
543+
RelationshipDescription relationshipDescription, Collection<Relationship> relationshipsFromResult,
544+
Collection<Node> nodesFromResult, @Nullable NodeDescription<?> nodeDescription) {
528545

529546
String typeOfRelationship = relationshipDescription.getType();
530547
String sourceLabel = relationshipDescription.getSource().getPrimaryLabel();
@@ -558,8 +575,13 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
558575
mappedObjectHandler = (type, mappedObject) -> value.add(mappedObject);
559576
}
560577

561-
String collectionName =
562-
relationshipDescription.generateRelatedNodesCollectionName(relationshipDescription.getSource());
578+
// Generate name driven by the (nullable) NodeDescription of necessary,
579+
// because it might contain associations not named CONCRETE_TYPE_TARGET but ABSTRACT_TYPE_TARGET
580+
String collectionName = relationshipDescription.generateRelatedNodesCollectionName(
581+
nodeDescription != null
582+
? nodeDescription
583+
: relationshipDescription.getSource()
584+
);
563585

564586
Value list = values.get(collectionName);
565587

@@ -672,7 +694,9 @@ private Collection<Node> extractNodes(MapAccessor allValues) {
672694
return allNodesInResult;
673695
}
674696

675-
private Collection<Relationship> extractMatchingRelationships(Collection<Relationship> relationshipsFromResult, RelationshipDescription relationshipDescription, String typeOfRelationship, Predicate<Relationship> relationshipPredicate) {
697+
private Collection<Relationship> extractMatchingRelationships(Collection<Relationship> relationshipsFromResult,
698+
RelationshipDescription relationshipDescription, String typeOfRelationship,
699+
Predicate<Relationship> relationshipPredicate) {
676700

677701
Predicate<Relationship> onlyWithMatchingType = r -> r.type().equals(typeOfRelationship) || relationshipDescription.isDynamic();
678702
return relationshipsFromResult.stream()

src/test/java/org/springframework/data/neo4j/integration/imperative/InheritanceMappingIT.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,27 @@ void shouldMatchPolymorphicKotlinInterfacesWhenFetchingAll(@Autowired CinemaRepo
405405
});
406406
}
407407

408+
@Test // GH-2452
409+
void asdf(@Autowired ParentClassWithRelationshipRepository repository) {
410+
411+
long childId;
412+
try (Session session = driver.session(bookmarkCapture.createSessionConfig())) {
413+
childId = session
414+
.run("CREATE (c:CCWR:PCWR{name:'child'})-[:LIVES_IN]->(:Continent:BaseTerritory:BaseEntity{nameEn:'continent', continentProperty:'small'}) return id(c) as id")
415+
.single().get(0).asLong();
416+
bookmarkCapture.seedWith(session.lastBookmark());
417+
}
418+
419+
Inheritance.ParentClassWithRelationship potentialChildClass = repository.findById(childId).get();
420+
assertThat(potentialChildClass).isInstanceOf(Inheritance.ChildClassWithRelationship.class);
421+
422+
Inheritance.ChildClassWithRelationship child = (Inheritance.ChildClassWithRelationship) potentialChildClass;
423+
assertThat(child.name).isEqualTo("child");
424+
425+
assertThat(child.continent).isNotNull();
426+
assertThat(child.continent.continentProperty).isEqualTo("small");
427+
}
428+
408429
private Consumer<Inheritance.ParentModel2> twoDifferentInterfacesHaveBeenLoaded() {
409430
return d -> {
410431
assertThat(d.getIsRelatedTo()).hasSize(2);
@@ -455,6 +476,9 @@ interface PetsRepository extends Neo4jRepository<AbstractPet, Long> {
455476

456477
}
457478

479+
interface ParentClassWithRelationshipRepository extends Neo4jRepository<Inheritance.ParentClassWithRelationship, Long> {
480+
}
481+
458482
interface BuildingRepository extends Neo4jRepository<Inheritance.Building, Long> {}
459483

460484
interface TerritoryRepository extends Neo4jRepository<Inheritance.BaseTerritory, Long> {}

src/test/java/org/springframework/data/neo4j/integration/shared/common/Inheritance.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,5 +925,37 @@ public void setIsActiveIn(
925925
}
926926
}
927927

928+
/**
929+
* Parent class with relationship definition in the constructor
930+
*/
931+
@Node("PCWR")
932+
public static abstract class ParentClassWithRelationship {
933+
934+
@Id
935+
@GeneratedValue
936+
public final Long id;
937+
938+
@Relationship("LIVES_IN")
939+
public final Continent continent;
940+
941+
public ParentClassWithRelationship(Long id, Continent continent) {
942+
this.id = id;
943+
this.continent = continent;
944+
}
945+
}
946+
947+
/**
948+
* Child class with relationship definition in the constructor
949+
*/
950+
@Node("CCWR")
951+
public static class ChildClassWithRelationship extends ParentClassWithRelationship {
952+
953+
public String name;
954+
955+
public ChildClassWithRelationship(Long id, Continent continent) {
956+
super(id, continent);
957+
}
958+
}
959+
928960

929961
}

0 commit comments

Comments
 (0)