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.
This commit is contained in:
@@ -55,7 +55,7 @@ import org.springframework.util.Assert;
|
||||
* @since 6.0.9
|
||||
*/
|
||||
@API(status = API.Status.INTERNAL, since = "6.0.9")
|
||||
final class TemplateSupport {
|
||||
public final class TemplateSupport {
|
||||
|
||||
enum FetchType {
|
||||
|
||||
@@ -64,11 +64,11 @@ final class TemplateSupport {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Class<?> findCommonElementType(Iterable<?> collection) {
|
||||
public static Class<?> findCommonElementType(Iterable<?> collection) {
|
||||
|
||||
List<Class<?>> allClasses = StreamSupport.stream(collection.spliterator(), true)
|
||||
Collection<Class<?>> allClasses = StreamSupport.stream(collection.spliterator(), true)
|
||||
.filter(o -> o != null)
|
||||
.map(Object::getClass).collect(Collectors.toList());
|
||||
.map(Object::getClass).collect(Collectors.toSet());
|
||||
|
||||
Class<?> candidate = null;
|
||||
for (Class<?> type : allClasses) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.repository.query;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -28,6 +29,7 @@ import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.neo4j.driver.Value;
|
||||
@@ -35,11 +37,13 @@ import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.geo.Box;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Metrics;
|
||||
import org.springframework.data.neo4j.core.TemplateSupport;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jSimpleTypes;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
import org.springframework.data.neo4j.core.mapping.EntityInstanceWithSource;
|
||||
@@ -181,6 +185,20 @@ abstract class Neo4jQuerySupport {
|
||||
return convertBoundingBox((BoundingBox) parameter);
|
||||
}
|
||||
|
||||
Class<?> type;
|
||||
if (parameter instanceof Collection && mappingContext
|
||||
.hasPersistentEntityFor(TemplateSupport.findCommonElementType((Collection) parameter))) {
|
||||
|
||||
EntityWriter<Object, Map<String, Object>> objectMapEntityWriter = Neo4jNestedMapEntityWriter
|
||||
.forContext(mappingContext);
|
||||
|
||||
return ((Collection<?>) parameter).stream().map(v -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
objectMapEntityWriter.write(v, result);
|
||||
return result;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (mappingContext.hasPersistentEntityFor(parameter.getClass())) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
@@ -86,12 +86,11 @@ import org.springframework.data.neo4j.core.convert.Neo4jConversions;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
|
||||
import org.springframework.data.neo4j.integration.imperative.repositories.FlightRepository;
|
||||
import org.springframework.data.neo4j.integration.imperative.repositories.PersonRepository;
|
||||
import org.springframework.data.neo4j.integration.imperative.repositories.PersonWithNoConstructorRepository;
|
||||
import org.springframework.data.neo4j.integration.imperative.repositories.PersonWithWitherRepository;
|
||||
import org.springframework.data.neo4j.integration.imperative.repositories.ThingRepository;
|
||||
import org.springframework.data.neo4j.integration.shared.common.Flight;
|
||||
import org.springframework.data.neo4j.integration.imperative.repositories.FlightRepository;
|
||||
import org.springframework.data.neo4j.integration.shared.common.AltHobby;
|
||||
import org.springframework.data.neo4j.integration.shared.common.AltLikedByPersonRelationship;
|
||||
import org.springframework.data.neo4j.integration.shared.common.AltPerson;
|
||||
@@ -110,6 +109,7 @@ import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDyna
|
||||
import org.springframework.data.neo4j.integration.shared.common.EntityWithConvertedId;
|
||||
import org.springframework.data.neo4j.integration.shared.common.EntityWithRelationshipPropertiesPath;
|
||||
import org.springframework.data.neo4j.integration.shared.common.ExtendedParentNode;
|
||||
import org.springframework.data.neo4j.integration.shared.common.Flight;
|
||||
import org.springframework.data.neo4j.integration.shared.common.Friend;
|
||||
import org.springframework.data.neo4j.integration.shared.common.FriendshipRelationship;
|
||||
import org.springframework.data.neo4j.integration.shared.common.Hobby;
|
||||
@@ -1909,17 +1909,7 @@ class RepositoryIT {
|
||||
|
||||
Assumptions.assumeTrue(ServerVersion.version(driver).greaterThanOrEqual(ServerVersion.v4_1_0));
|
||||
|
||||
PersonWithRelationship p = new PersonWithRelationship();
|
||||
p.setName("A Person");
|
||||
p.setId(4711L);
|
||||
Hobby h = new Hobby();
|
||||
h.setName("A Hobby");
|
||||
p.setHobbies(h);
|
||||
p.setPets(Arrays.asList(new Pet("A"), new Pet("B")));
|
||||
|
||||
Club club = new Club();
|
||||
club.setName("C27");
|
||||
p.setClub(club);
|
||||
PersonWithRelationship p = createNewPerson("A Person", createNewClub("C27"));
|
||||
|
||||
PersonWithRelationship newPerson = repository.createWithCustomQuery(p);
|
||||
newPerson = repository.findById(newPerson.getId()).get();
|
||||
@@ -1929,6 +1919,46 @@ class RepositoryIT {
|
||||
assertThat(newPerson.getClub().getName()).isEqualTo("C27");
|
||||
}
|
||||
|
||||
private PersonWithRelationship createNewPerson(String name, Club club) {
|
||||
PersonWithRelationship p = new PersonWithRelationship();
|
||||
p.setName(name);
|
||||
p.setId(4711L);
|
||||
Hobby h = new Hobby();
|
||||
h.setName("A Hobby");
|
||||
p.setHobbies(h);
|
||||
p.setPets(Arrays.asList(new Pet("A"), new Pet("B")));
|
||||
|
||||
p.setClub(club);
|
||||
return p;
|
||||
}
|
||||
|
||||
private Club createNewClub(String name) {
|
||||
Club club = new Club();
|
||||
club.setName(name);
|
||||
return club;
|
||||
}
|
||||
|
||||
@Test // DATAGRAPH-2292
|
||||
void createWithCustomQueryShouldWorkWithCollectionsOfNestedObjects(@Autowired Driver driver, @Autowired RelationshipRepository repository) {
|
||||
|
||||
Assumptions.assumeTrue(ServerVersion.version(driver).greaterThanOrEqual(ServerVersion.v4_1_0));
|
||||
|
||||
Club c27 = createNewClub("C27");
|
||||
Set<PersonWithRelationship> people = new HashSet<>();
|
||||
people.add(createNewPerson("A person", c27));
|
||||
people.add(createNewPerson("Another person", c27));
|
||||
|
||||
List<PersonWithRelationship> newPeople = repository.createManyWithCustomQuery(people);
|
||||
assertThat(newPeople).hasSize(2)
|
||||
.allSatisfy(p -> {
|
||||
PersonWithRelationship newPerson = repository.findById(p.getId()).get();
|
||||
assertThat(newPerson.getName()).isEqualTo(p.getName());
|
||||
assertThat(newPerson.getHobbies().getName()).isEqualTo("A Hobby");
|
||||
assertThat(newPerson.getPets()).extracting(Pet::getName).containsExactlyInAnyOrder("A", "B");
|
||||
assertThat(newPerson.getClub().getName()).isEqualTo("C27");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveSingleEntityWithRelationships(@Autowired RelationshipRepository repository) {
|
||||
|
||||
@@ -1937,8 +1967,7 @@ class RepositoryIT {
|
||||
Hobby hobby = new Hobby();
|
||||
hobby.setName("Music");
|
||||
person.setHobbies(hobby);
|
||||
Club club = new Club();
|
||||
club.setName("ClownsClub");
|
||||
Club club = createNewClub("ClownsClub");
|
||||
person.setClub(club);
|
||||
Pet pet1 = new Pet("Jerry");
|
||||
Pet pet2 = new Pet("Tom");
|
||||
@@ -4142,6 +4171,37 @@ class RepositoryIT {
|
||||
+ "RETURN n, collect(r), collect(p)")
|
||||
PersonWithRelationship createWithCustomQuery(PersonWithRelationship p);
|
||||
|
||||
@Transactional
|
||||
@Query("UNWIND $0 AS pwr WITH pwr CREATE (n:PersonWithRelationship) \n"
|
||||
+ "SET n.name = pwr.__properties__.name \n"
|
||||
+ "WITH pwr, n, id(n) as parentId\n"
|
||||
+ "UNWIND pwr.__properties__.Has as x\n"
|
||||
+ "CALL { WITH x, parentId\n"
|
||||
+ " \n"
|
||||
+ " WITH x, parentId\n"
|
||||
+ " MATCH (_) \n"
|
||||
+ " WHERE id(_) = parentId AND x.__labels__[0] = 'Pet'\n"
|
||||
+ " CREATE (p:Pet {name: x.__properties__.name}) <- [r:Has] - (_)\n"
|
||||
+ " RETURN p, r\n"
|
||||
+ " \n"
|
||||
+ " UNION\n"
|
||||
+ " WITH x, parentId\n"
|
||||
+ " MATCH (_) \n"
|
||||
+ " WHERE id(_) = parentId AND x.__labels__[0] = 'Hobby'\n"
|
||||
+ " CREATE (p:Hobby {name: x.__properties__.name}) <- [r:Has] - (_)\n"
|
||||
+ " RETURN p, r\n"
|
||||
+ "\n"
|
||||
+ " UNION\n"
|
||||
+ " WITH x, parentId\n"
|
||||
+ " MATCH (_) \n"
|
||||
+ " WHERE id(_) = parentId AND x.__labels__[0] = 'Club'\n"
|
||||
+ " CREATE (p:Club {name: x.__properties__.name}) - [r:Has] -> (_)\n"
|
||||
+ " RETURN p, r\n"
|
||||
+ "\n"
|
||||
+ "}\n"
|
||||
+ "RETURN n, collect(r), collect(p)")
|
||||
List<PersonWithRelationship> createManyWithCustomQuery(Collection<PersonWithRelationship> p);
|
||||
|
||||
PersonWithRelationship.PersonWithHobby findDistinctByHobbiesName(String hobbyName);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user