diff --git a/src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java b/src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java index b2fc1ddee..01ea82b6b 100644 --- a/src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java +++ b/src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java @@ -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> allClasses = StreamSupport.stream(collection.spliterator(), true) + Collection> 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) { diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java index 1b8fdeeb9..a32015c4e 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java @@ -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> objectMapEntityWriter = Neo4jNestedMapEntityWriter + .forContext(mappingContext); + + return ((Collection) parameter).stream().map(v -> { + Map result = new HashMap<>(); + objectMapEntityWriter.write(v, result); + return result; + }).collect(Collectors.toList()); + } + if (mappingContext.hasPersistentEntityFor(parameter.getClass())) { Map result = new HashMap<>(); diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java index c61548cb8..262bba443 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java @@ -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 people = new HashSet<>(); + people.add(createNewPerson("A person", c27)); + people.add(createNewPerson("Another person", c27)); + + List 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 createManyWithCustomQuery(Collection p); + PersonWithRelationship.PersonWithHobby findDistinctByHobbiesName(String hobbyName); }