Skip to content

Commit b882a12

Browse files
GH-2292 - Support collections of entities as parameters to custom repository queries.
This adds support for collections of entities as parameters. As of know, we try to figure out the common element of that collection and see if we have fitting entity type. If so, all elements are converted into `Map<String, Object>` and put into a list. Another approach would be trying to figure out the declared, resolvable type and than the generic type of a collection, but that would require much bigger changes in the infrastructure. An additional benefit: Heterogenous collections are supported as well to some extend. If this solution posses to be too slow, we can still investigate the generics approach. This closes #2292.
1 parent 31d4835 commit b882a12

File tree

3 files changed

+95
-17
lines changed

3 files changed

+95
-17
lines changed

src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
* @since 6.0.9
5656
*/
5757
@API(status = API.Status.INTERNAL, since = "6.0.9")
58-
final class TemplateSupport {
58+
public final class TemplateSupport {
5959

6060
enum FetchType {
6161

@@ -64,11 +64,11 @@ enum FetchType {
6464
}
6565

6666
@Nullable
67-
static Class<?> findCommonElementType(Iterable<?> collection) {
67+
public static Class<?> findCommonElementType(Iterable<?> collection) {
6868

69-
List<Class<?>> allClasses = StreamSupport.stream(collection.spliterator(), true)
69+
Collection<Class<?>> allClasses = StreamSupport.stream(collection.spliterator(), true)
7070
.filter(o -> o != null)
71-
.map(Object::getClass).collect(Collectors.toList());
71+
.map(Object::getClass).collect(Collectors.toSet());
7272

7373
Class<?> candidate = null;
7474
for (Class<?> type : allClasses) {

src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java

+18
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.time.Instant;
1919
import java.time.ZoneOffset;
2020
import java.util.Arrays;
21+
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.HashMap;
2324
import java.util.HashSet;
@@ -28,18 +29,21 @@
2829
import java.util.function.BiFunction;
2930
import java.util.function.Function;
3031
import java.util.function.Supplier;
32+
import java.util.stream.Collectors;
3133

3234
import org.apache.commons.logging.LogFactory;
3335
import org.neo4j.driver.Value;
3436
import org.neo4j.driver.Values;
3537
import org.neo4j.driver.types.MapAccessor;
3638
import org.neo4j.driver.types.TypeSystem;
3739
import org.springframework.core.log.LogAccessor;
40+
import org.springframework.data.convert.EntityWriter;
3841
import org.springframework.data.domain.Range;
3942
import org.springframework.data.geo.Box;
4043
import org.springframework.data.geo.Circle;
4144
import org.springframework.data.geo.Distance;
4245
import org.springframework.data.geo.Metrics;
46+
import org.springframework.data.neo4j.core.TemplateSupport;
4347
import org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes;
4448
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
4549
import org.springframework.data.neo4j.core.mapping.EntityInstanceWithSource;
@@ -181,6 +185,20 @@ final Object convertParameter(Object parameter, @Nullable Function<Object, Value
181185
return convertBoundingBox((BoundingBox) parameter);
182186
}
183187

188+
Class<?> type;
189+
if (parameter instanceof Collection && mappingContext
190+
.hasPersistentEntityFor(TemplateSupport.findCommonElementType((Collection) parameter))) {
191+
192+
EntityWriter<Object, Map<String, Object>> objectMapEntityWriter = Neo4jNestedMapEntityWriter
193+
.forContext(mappingContext);
194+
195+
return ((Collection<?>) parameter).stream().map(v -> {
196+
Map<String, Object> result = new HashMap<>();
197+
objectMapEntityWriter.write(v, result);
198+
return result;
199+
}).collect(Collectors.toList());
200+
}
201+
184202
if (mappingContext.hasPersistentEntityFor(parameter.getClass())) {
185203

186204
Map<String, Object> result = new HashMap<>();

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

+73-13
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,11 @@
8686
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
8787
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
8888
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
89+
import org.springframework.data.neo4j.integration.imperative.repositories.FlightRepository;
8990
import org.springframework.data.neo4j.integration.imperative.repositories.PersonRepository;
9091
import org.springframework.data.neo4j.integration.imperative.repositories.PersonWithNoConstructorRepository;
9192
import org.springframework.data.neo4j.integration.imperative.repositories.PersonWithWitherRepository;
9293
import org.springframework.data.neo4j.integration.imperative.repositories.ThingRepository;
93-
import org.springframework.data.neo4j.integration.shared.common.Flight;
94-
import org.springframework.data.neo4j.integration.imperative.repositories.FlightRepository;
9594
import org.springframework.data.neo4j.integration.shared.common.AltHobby;
9695
import org.springframework.data.neo4j.integration.shared.common.AltLikedByPersonRelationship;
9796
import org.springframework.data.neo4j.integration.shared.common.AltPerson;
@@ -110,6 +109,7 @@
110109
import org.springframework.data.neo4j.integration.shared.common.EntityWithConvertedId;
111110
import org.springframework.data.neo4j.integration.shared.common.EntityWithRelationshipPropertiesPath;
112111
import org.springframework.data.neo4j.integration.shared.common.ExtendedParentNode;
112+
import org.springframework.data.neo4j.integration.shared.common.Flight;
113113
import org.springframework.data.neo4j.integration.shared.common.Friend;
114114
import org.springframework.data.neo4j.integration.shared.common.FriendshipRelationship;
115115
import org.springframework.data.neo4j.integration.shared.common.Hobby;
@@ -1909,24 +1909,54 @@ void createWithCustomQueryShouldWorkWithNestedObjects(@Autowired Driver driver,
19091909

19101910
Assumptions.assumeTrue(ServerVersion.version(driver).greaterThanOrEqual(ServerVersion.v4_1_0));
19111911

1912+
PersonWithRelationship p = createNewPerson("A Person", createNewClub("C27"));
1913+
1914+
PersonWithRelationship newPerson = repository.createWithCustomQuery(p);
1915+
newPerson = repository.findById(newPerson.getId()).get();
1916+
assertThat(newPerson.getName()).isEqualTo(p.getName());
1917+
assertThat(newPerson.getHobbies().getName()).isEqualTo("A Hobby");
1918+
assertThat(newPerson.getPets()).extracting(Pet::getName).containsExactlyInAnyOrder("A", "B");
1919+
assertThat(newPerson.getClub().getName()).isEqualTo("C27");
1920+
}
1921+
1922+
private PersonWithRelationship createNewPerson(String name, Club club) {
19121923
PersonWithRelationship p = new PersonWithRelationship();
1913-
p.setName("A Person");
1924+
p.setName(name);
19141925
p.setId(4711L);
19151926
Hobby h = new Hobby();
19161927
h.setName("A Hobby");
19171928
p.setHobbies(h);
19181929
p.setPets(Arrays.asList(new Pet("A"), new Pet("B")));
19191930

1920-
Club club = new Club();
1921-
club.setName("C27");
19221931
p.setClub(club);
1932+
return p;
1933+
}
19231934

1924-
PersonWithRelationship newPerson = repository.createWithCustomQuery(p);
1925-
newPerson = repository.findById(newPerson.getId()).get();
1926-
assertThat(newPerson.getName()).isEqualTo(p.getName());
1927-
assertThat(newPerson.getHobbies().getName()).isEqualTo("A Hobby");
1928-
assertThat(newPerson.getPets()).extracting(Pet::getName).containsExactlyInAnyOrder("A", "B");
1929-
assertThat(newPerson.getClub().getName()).isEqualTo("C27");
1935+
private Club createNewClub(String name) {
1936+
Club club = new Club();
1937+
club.setName(name);
1938+
return club;
1939+
}
1940+
1941+
@Test // DATAGRAPH-2292
1942+
void createWithCustomQueryShouldWorkWithCollectionsOfNestedObjects(@Autowired Driver driver, @Autowired RelationshipRepository repository) {
1943+
1944+
Assumptions.assumeTrue(ServerVersion.version(driver).greaterThanOrEqual(ServerVersion.v4_1_0));
1945+
1946+
Club c27 = createNewClub("C27");
1947+
Set<PersonWithRelationship> people = new HashSet<>();
1948+
people.add(createNewPerson("A person", c27));
1949+
people.add(createNewPerson("Another person", c27));
1950+
1951+
List<PersonWithRelationship> newPeople = repository.createManyWithCustomQuery(people);
1952+
assertThat(newPeople).hasSize(2)
1953+
.allSatisfy(p -> {
1954+
PersonWithRelationship newPerson = repository.findById(p.getId()).get();
1955+
assertThat(newPerson.getName()).isEqualTo(p.getName());
1956+
assertThat(newPerson.getHobbies().getName()).isEqualTo("A Hobby");
1957+
assertThat(newPerson.getPets()).extracting(Pet::getName).containsExactlyInAnyOrder("A", "B");
1958+
assertThat(newPerson.getClub().getName()).isEqualTo("C27");
1959+
});
19301960
}
19311961

19321962
@Test
@@ -1937,8 +1967,7 @@ void saveSingleEntityWithRelationships(@Autowired RelationshipRepository reposit
19371967
Hobby hobby = new Hobby();
19381968
hobby.setName("Music");
19391969
person.setHobbies(hobby);
1940-
Club club = new Club();
1941-
club.setName("ClownsClub");
1970+
Club club = createNewClub("ClownsClub");
19421971
person.setClub(club);
19431972
Pet pet1 = new Pet("Jerry");
19441973
Pet pet2 = new Pet("Tom");
@@ -4142,6 +4171,37 @@ interface RelationshipRepository extends Neo4jRepository<PersonWithRelationship,
41424171
+ "RETURN n, collect(r), collect(p)")
41434172
PersonWithRelationship createWithCustomQuery(PersonWithRelationship p);
41444173

4174+
@Transactional
4175+
@Query("UNWIND $0 AS pwr WITH pwr CREATE (n:PersonWithRelationship) \n"
4176+
+ "SET n.name = pwr.__properties__.name \n"
4177+
+ "WITH pwr, n, id(n) as parentId\n"
4178+
+ "UNWIND pwr.__properties__.Has as x\n"
4179+
+ "CALL { WITH x, parentId\n"
4180+
+ " \n"
4181+
+ " WITH x, parentId\n"
4182+
+ " MATCH (_) \n"
4183+
+ " WHERE id(_) = parentId AND x.__labels__[0] = 'Pet'\n"
4184+
+ " CREATE (p:Pet {name: x.__properties__.name}) <- [r:Has] - (_)\n"
4185+
+ " RETURN p, r\n"
4186+
+ " \n"
4187+
+ " UNION\n"
4188+
+ " WITH x, parentId\n"
4189+
+ " MATCH (_) \n"
4190+
+ " WHERE id(_) = parentId AND x.__labels__[0] = 'Hobby'\n"
4191+
+ " CREATE (p:Hobby {name: x.__properties__.name}) <- [r:Has] - (_)\n"
4192+
+ " RETURN p, r\n"
4193+
+ "\n"
4194+
+ " UNION\n"
4195+
+ " WITH x, parentId\n"
4196+
+ " MATCH (_) \n"
4197+
+ " WHERE id(_) = parentId AND x.__labels__[0] = 'Club'\n"
4198+
+ " CREATE (p:Club {name: x.__properties__.name}) - [r:Has] -> (_)\n"
4199+
+ " RETURN p, r\n"
4200+
+ "\n"
4201+
+ "}\n"
4202+
+ "RETURN n, collect(r), collect(p)")
4203+
List<PersonWithRelationship> createManyWithCustomQuery(Collection<PersonWithRelationship> p);
4204+
41454205
PersonWithRelationship.PersonWithHobby findDistinctByHobbiesName(String hobbyName);
41464206
}
41474207

0 commit comments

Comments
 (0)