Skip to content

Commit 1a6d18a

Browse files
GH-2262 - Ensure related entities with heterogenous types and a common base class are loaded correctly.
This adds a test for the scenario in the ticket.
1 parent 56240cf commit 1a6d18a

File tree

2 files changed

+151
-15
lines changed

2 files changed

+151
-15
lines changed

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

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@
4242
import java.util.Collection;
4343
import java.util.Collections;
4444
import java.util.List;
45+
import java.util.Map;
4546
import java.util.Optional;
47+
import java.util.function.Consumer;
48+
import java.util.stream.Collectors;
4649

4750
import static org.assertj.core.api.Assertions.assertThat;
4851

@@ -89,15 +92,7 @@ void relationshipsShouldHaveCorrectTypes(@Autowired BuildingRepository repositor
8992
@Test // GH-2138
9093
void collectionsShouldHaveCorrectTypes(@Autowired TerritoryRepository repository) {
9194

92-
Long territoryId;
93-
try (Session session = driver.session()) {
94-
territoryId = session.run("CREATE (c:Country:BaseTerritory:BaseEntity{nameEn:'country'}) " +
95-
"CREATE (c)-[:LINK]->(:Country:BaseTerritory:BaseEntity{nameEn:'anotherCountry', countryProperty:'large'}) " +
96-
"CREATE (c)-[:LINK]->(:Continent:BaseTerritory:BaseEntity{nameEn:'continent', continentProperty:'small'}) " +
97-
"CREATE (c)-[:LINK]->(:GenericTerritory:BaseTerritory:BaseEntity{nameEn:'generic'}) " +
98-
"return id(c) as id").single()
99-
.get(0).asLong();
100-
}
95+
Long territoryId = createDivisionAndTerritories().get("territoryId").asLong();
10196

10297
Inheritance.BaseTerritory territory = repository.findById(territoryId).get();
10398

@@ -117,12 +112,7 @@ void collectionsShouldHaveCorrectTypes(@Autowired TerritoryRepository repository
117112
@Test // GH-2138
118113
void resultCollectionShouldHaveCorrectTypes(@Autowired TerritoryRepository repository) {
119114

120-
try (Session session = driver.session()) {
121-
session.run("CREATE (c:Country:BaseTerritory:BaseEntity{nameEn:'country', countryProperty:'baseCountry'}) " +
122-
"CREATE (c)-[:LINK]->(:Country:BaseTerritory:BaseEntity{nameEn:'anotherCountry', countryProperty:'large'}) " +
123-
"CREATE (c)-[:LINK]->(:Continent:BaseTerritory:BaseEntity{nameEn:'continent', continentProperty:'small'}) " +
124-
"CREATE (c)-[:LINK]->(:GenericTerritory:BaseTerritory:BaseEntity{nameEn:'generic'})").consume();
125-
}
115+
createDivisionAndTerritories();
126116

127117
List<Inheritance.BaseTerritory> territories = repository.findAll();
128118

@@ -324,6 +314,98 @@ void mixedInterfaces(@Autowired Neo4jTemplate template) {
324314
}
325315
}
326316

317+
@Test // GH-2262
318+
void shouldMatchPolymorphicClassesWhenFetchedById(@Autowired DivisionRepository repository) {
319+
320+
Record divisionAndTerritoryId = createDivisionAndTerritories();
321+
322+
Optional<Inheritance.Division> optionalDivision = repository.findById(divisionAndTerritoryId.get("divisionId").asLong());
323+
assertThat(optionalDivision).isPresent();
324+
assertThat(optionalDivision).hasValueSatisfying(twoDifferentClassesHaveBeenLoaded());
325+
}
326+
327+
@Test // GH-2262
328+
void shouldMatchPolymorphicClassesWhenFetchingAll(@Autowired DivisionRepository repository) {
329+
330+
createDivisionAndTerritories();
331+
332+
List<Inheritance.Division> divisions = repository.findAll();
333+
assertThat(divisions).hasSize(1);
334+
assertThat(divisions).first().satisfies(twoDifferentClassesHaveBeenLoaded());
335+
}
336+
337+
private Consumer<Inheritance.Division> twoDifferentClassesHaveBeenLoaded() {
338+
return d -> {
339+
assertThat(d.getIsActiveIn()).hasSize(2);
340+
assertThat(d.getIsActiveIn()).extracting(Inheritance.BaseTerritory::getNameEn)
341+
.containsExactlyInAnyOrder("anotherCountry", "continent");
342+
Map<String, Class> classByName = d.getIsActiveIn().stream()
343+
.collect(Collectors.toMap(Inheritance.BaseTerritory::getNameEn, v -> v.getClass()));
344+
assertThat(classByName).containsEntry("anotherCountry", Inheritance.Country.class);
345+
assertThat(classByName).containsEntry("continent", Inheritance.Continent.class);
346+
};
347+
}
348+
349+
@Test // GH-2262
350+
void shouldMatchPolymorphicInterfacesWhenFetchedById(@Autowired ParentModelRepository repository) {
351+
352+
Record record = createRelationsToDifferentImplementations();
353+
354+
Optional<Inheritance.ParentModel2> optionalDivision = repository.findById(record.get(0).asNode().id());
355+
assertThat(optionalDivision).isPresent();
356+
assertThat(optionalDivision).hasValueSatisfying(twoDifferentInterfacesHaveBeenLoaded());
357+
}
358+
359+
@Test // GH-2262
360+
void shouldMatchPolymorphicInterfacesWhenFetchingAll(@Autowired ParentModelRepository repository) {
361+
362+
createRelationsToDifferentImplementations();
363+
364+
List<Inheritance.ParentModel2> divisions = repository.findAll();
365+
assertThat(divisions).hasSize(1);
366+
assertThat(divisions).first().satisfies(twoDifferentInterfacesHaveBeenLoaded());
367+
}
368+
369+
private Consumer<Inheritance.ParentModel2> twoDifferentInterfacesHaveBeenLoaded() {
370+
return d -> {
371+
assertThat(d.getIsRelatedTo()).hasSize(2);
372+
assertThat(d.getIsRelatedTo()).extracting(Inheritance.SomeInterface3::getName)
373+
.containsExactlyInAnyOrder("3a", "3b");
374+
Map<String, Class> classByName = d.getIsRelatedTo().stream()
375+
.collect(Collectors.toMap(Inheritance.SomeInterface3::getName, v -> v.getClass()));
376+
assertThat(classByName).containsEntry("3a", Inheritance.SomeInterfaceImpl3a.class);
377+
assertThat(classByName).containsEntry("3b", Inheritance.SomeInterfaceImpl3b.class);
378+
};
379+
}
380+
381+
private Record createDivisionAndTerritories() {
382+
Record result;
383+
try (Session session = driver.session()) {
384+
385+
result = session.run("CREATE (c:Country:BaseTerritory:BaseEntity{nameEn:'country', countryProperty:'baseCountry'}) " +
386+
"CREATE (c)-[:LINK]->(ca:Country:BaseTerritory:BaseEntity{nameEn:'anotherCountry', countryProperty:'large'}) " +
387+
"CREATE (c)-[:LINK]->(cb:Continent:BaseTerritory:BaseEntity{nameEn:'continent', continentProperty:'small'}) " +
388+
"CREATE (c)-[:LINK]->(:GenericTerritory:BaseTerritory:BaseEntity{nameEn:'generic'}) " +
389+
"CREATE (d:Division:BaseEntity{name:'Division'}) " +
390+
"CREATE (d) -[:IS_ACTIVE_IN] -> (ca)" +
391+
"CREATE (d) -[:IS_ACTIVE_IN] -> (cb)" +
392+
"RETURN id(d) as divisionId, id(c) as territoryId").single();
393+
}
394+
return result;
395+
}
396+
397+
private Record createRelationsToDifferentImplementations() {
398+
Record result;
399+
try (Session session = driver.session()) {
400+
401+
result = session.run("CREATE (p:ParentModel2) " +
402+
"CREATE (p)-[:IS_RELATED_TO]->(:SomeInterface3:SomeInterface3a {name: '3a'}) " +
403+
"CREATE (p)-[:IS_RELATED_TO]->(:SomeInterface3:SomeInterface3b {name: '3b'}) " +
404+
"RETURN p").single();
405+
}
406+
return result;
407+
}
408+
327409
interface PetsRepository extends Neo4jRepository<AbstractPet, Long> {
328410

329411
@Query("MATCH (n {name: $name}) RETURN n")
@@ -335,6 +417,10 @@ interface BuildingRepository extends Neo4jRepository<Inheritance.Building, Long>
335417

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

420+
interface DivisionRepository extends Neo4jRepository<Inheritance.Division, Long> {}
421+
422+
interface ParentModelRepository extends Neo4jRepository<Inheritance.ParentModel2, Long> {}
423+
338424
@Configuration
339425
@EnableNeo4jRepositories(considerNestedRepositories = true)
340426
@EnableTransactionManagement

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,31 @@ public void setRelated2(SomeInterface3 related2) {
279279
}
280280
// end::interface3[]
281281

282+
/**
283+
* A holder for a list of different interface implementations, see GH-2262.
284+
*/
285+
@Node
286+
public static class ParentModel2 {
287+
288+
@Id
289+
@GeneratedValue
290+
private Long id;
291+
292+
private List<SomeInterface3> isRelatedTo;
293+
294+
public Long getId() {
295+
return id;
296+
}
297+
298+
public List<SomeInterface3> getIsRelatedTo() {
299+
return isRelatedTo;
300+
}
301+
302+
public void setIsRelatedTo(List<SomeInterface3> isRelatedTo) {
303+
this.isRelatedTo = isRelatedTo;
304+
}
305+
}
306+
282307
/**
283308
* Interface to get implemented with the one below.
284309
*/
@@ -780,6 +805,10 @@ public BaseTerritory(String nameEn) {
780805
this.nameEn = nameEn;
781806
}
782807

808+
public String getNameEn() {
809+
return nameEn;
810+
}
811+
783812
@Override
784813
public boolean equals(Object o) {
785814
if (this == o) {
@@ -876,4 +905,25 @@ public int hashCode() {
876905
return Objects.hash(super.hashCode(), continentProperty);
877906
}
878907
}
908+
909+
/**
910+
* A parent object for some territories, used to test whether those are loaded correct in a polymorphic way.
911+
*/
912+
@Node
913+
public static class Division extends BaseEntity {
914+
915+
@Relationship
916+
List<BaseTerritory> isActiveIn;
917+
918+
public List<BaseTerritory> getIsActiveIn() {
919+
return isActiveIn;
920+
}
921+
922+
public void setIsActiveIn(
923+
List<BaseTerritory> isActiveIn) {
924+
this.isActiveIn = isActiveIn;
925+
}
926+
}
927+
928+
879929
}

0 commit comments

Comments
 (0)