Skip to content

Commit c0b2e57

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 and fixes #2262.
1 parent 2193c7a commit c0b2e57

File tree

2 files changed

+160
-24
lines changed

2 files changed

+160
-24
lines changed

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

Lines changed: 110 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
*/
1616
package org.springframework.data.neo4j.integration.imperative;
1717

18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import java.util.Collection;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Optional;
25+
import java.util.function.Consumer;
26+
import java.util.stream.Collectors;
27+
1828
import org.junit.jupiter.api.BeforeEach;
1929
import org.junit.jupiter.api.Test;
2030
import org.neo4j.driver.Driver;
@@ -46,13 +56,6 @@
4656
import org.springframework.transaction.annotation.Transactional;
4757
import org.springframework.transaction.support.TransactionTemplate;
4858

49-
import java.util.Collection;
50-
import java.util.Collections;
51-
import java.util.List;
52-
import java.util.Optional;
53-
54-
import static org.assertj.core.api.Assertions.assertThat;
55-
5659
/**
5760
* @author Gerrit Meier
5861
* @author Michael J. Simons
@@ -105,16 +108,7 @@ void relationshipsShouldHaveCorrectTypes(@Autowired BuildingRepository repositor
105108
@Test // GH-2138
106109
void collectionsShouldHaveCorrectTypes(@Autowired TerritoryRepository repository) {
107110

108-
Long territoryId;
109-
try (Session session = driver.session(bookmarkCapture.createSessionConfig())) {
110-
territoryId = session.run("CREATE (c:Country:BaseTerritory:BaseEntity{nameEn:'country'}) " +
111-
"CREATE (c)-[:LINK]->(:Country:BaseTerritory:BaseEntity{nameEn:'anotherCountry', countryProperty:'large'}) " +
112-
"CREATE (c)-[:LINK]->(:Continent:BaseTerritory:BaseEntity{nameEn:'continent', continentProperty:'small'}) " +
113-
"CREATE (c)-[:LINK]->(:GenericTerritory:BaseTerritory:BaseEntity{nameEn:'generic'}) " +
114-
"return id(c) as id").single()
115-
.get(0).asLong();
116-
bookmarkCapture.seedWith(session.lastBookmark());
117-
}
111+
Long territoryId = createDivisionAndTerritories().get("territoryId").asLong();
118112

119113
Inheritance.BaseTerritory territory = repository.findById(territoryId).get();
120114

@@ -134,13 +128,7 @@ void collectionsShouldHaveCorrectTypes(@Autowired TerritoryRepository repository
134128
@Test // GH-2138
135129
void resultCollectionShouldHaveCorrectTypes(@Autowired TerritoryRepository repository) {
136130

137-
try (Session session = driver.session(bookmarkCapture.createSessionConfig())) {
138-
session.run("CREATE (c:Country:BaseTerritory:BaseEntity{nameEn:'country', countryProperty:'baseCountry'}) " +
139-
"CREATE (c)-[:LINK]->(:Country:BaseTerritory:BaseEntity{nameEn:'anotherCountry', countryProperty:'large'}) " +
140-
"CREATE (c)-[:LINK]->(:Continent:BaseTerritory:BaseEntity{nameEn:'continent', continentProperty:'small'}) " +
141-
"CREATE (c)-[:LINK]->(:GenericTerritory:BaseTerritory:BaseEntity{nameEn:'generic'})").consume();
142-
bookmarkCapture.seedWith(session.lastBookmark());
143-
}
131+
createDivisionAndTerritories();
144132

145133
List<Inheritance.BaseTerritory> territories = repository.findAll();
146134

@@ -343,6 +331,100 @@ void mixedInterfaces(@Autowired Neo4jTemplate template) {
343331
}
344332
}
345333

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

348430
@Transactional(readOnly = true)
@@ -355,6 +437,10 @@ interface BuildingRepository extends Neo4jRepository<Inheritance.Building, Long>
355437

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

440+
interface DivisionRepository extends Neo4jRepository<Inheritance.Division, Long> {}
441+
442+
interface ParentModelRepository extends Neo4jRepository<Inheritance.ParentModel2, Long> {}
443+
358444
@Configuration
359445
@EnableNeo4jRepositories(considerNestedRepositories = true)
360446
@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
*/
@@ -779,6 +804,10 @@ public BaseTerritory(String nameEn) {
779804
this.nameEn = nameEn;
780805
}
781806

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

0 commit comments

Comments
 (0)