diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriter.java b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriter.java index 161b873b4..1455b64e4 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriter.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriter.java @@ -114,12 +114,10 @@ final class Neo4jNestedMapEntityWriter implements EntityWriter propertyMap = (Map) sink.get(Constants.NAME_OF_PROPERTIES_PARAM); entity.doWithProperties((PropertyHandler) p -> { if (p.isAnnotationPresent(TargetNode.class)) { - Map target = this.writeImpl(propertyAccessor.getProperty(p), new HashMap<>(), seenObjects, false); - propertyMap.put("__target__", Values.value(target)); - return; + Value target = Values.value(this.writeImpl(propertyAccessor.getProperty(p), new HashMap<>(), seenObjects, false)); + sink.put("__target__", target); } }); } diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/GH2323IT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/GH2323IT.java index 490d50603..328078cbc 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/GH2323IT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/GH2323IT.java @@ -19,10 +19,12 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.neo4j.driver.Driver; import org.neo4j.driver.Session; @@ -69,6 +71,21 @@ class GH2323IT { } } + @BeforeEach + protected void removeRelationships(@Autowired BookmarkCapture bookmarkCapture) { + try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig()); + Transaction transaction = session.beginTransaction(); + ) { + transaction.run("MATCH ()- [r:KNOWS]-() DELETE r").consume(); + transaction.run("MATCH (n:Language) DELETE n").consume(); + transaction.run("MATCH (n:Person {name: 'Gerrit'}) DETACH DELETE n").consume(); + transaction.run("unwind ['German', 'English'] as name create (n:Language {name: name}) return name") + .consume(); + transaction.commit(); + bookmarkCapture.seedWith(session.lastBookmark()); + } + } + @Test // GH-2323 void listOfRelationshipPropertiesShouldBeUnwindable(@Autowired PersonService personService) { Person person = personService.updateRel(personId, Arrays.asList("German")); @@ -84,10 +101,14 @@ class GH2323IT { void dontMixRelatedNodes(@Autowired PersonRepository repository, @Autowired BookmarkCapture bookmarkCapture) { String id; try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig()); - Transaction transaction = session.beginTransaction(); + Transaction transaction = session.beginTransaction(); ) { - id = transaction.run("CREATE (n:Person {id:randomUUID(), name: 'Gerrit'})-[:KNOWS]->(:Language{name:'English'}) return n.id").single().get(0).asString(); - transaction.run("MATCH (n:Person {name: 'Gerrit'}) MERGE (n)-[:MOTHER_TONGUE_IS]->(:Language{name:'German'})").consume(); + id = transaction.run( + "CREATE (n:Person {id:randomUUID(), name: 'Gerrit'})-[:KNOWS]->(:Language{name:'English'}) return n.id") + .single().get(0).asString(); + transaction.run( + "MATCH (n:Person {name: 'Gerrit'}) MERGE (n)-[:MOTHER_TONGUE_IS]->(:Language{name:'German'})") + .consume(); transaction.commit(); bookmarkCapture.seedWith(session.lastBookmark()); @@ -101,14 +122,40 @@ class GH2323IT { } } + @Test // GH-2537 + void ensureRelationshipsAreSerialized(@Autowired PersonService personService) { + + Optional optionalPerson = personService.updateRel2(personId, Arrays.asList("German")); + assertThat(optionalPerson).isPresent().hasValueSatisfying(person -> { + assertThat(person.getKnownLanguages()).hasSize(1); + assertThat(person.getKnownLanguages()).first().satisfies(knows -> { + assertThat(knows.getDescription()).isEqualTo("Some description"); + assertThat(knows.getLanguage()).extracting(Language::getName).isEqualTo("German"); + }); + }); + } + @Repository public interface PersonRepository extends Neo4jRepository { - @Query("UNWIND $relations As rel WITH rel " + - "CREATE (f:Person {id: $from}) - [r:KNOWS {description: rel.__properties__.description}] -> (t:Language {name: rel.__properties__.__target__.__id__}) " - + - "RETURN f, collect(r), collect(t)") + // Using separate id and than relationships on top level + @Query("" + + "UNWIND $relations As rel WITH rel " + + "MATCH (f:Person {id: $from}) " + + "MATCH (t:Language {name: rel.__target__.__id__}) " + + "CREATE (f)- [r:KNOWS {description: rel.__properties__.description}] -> (t) " + + "RETURN f, collect(r), collect(t)" + ) Person updateRel(@Param("from") String from, @Param("relations") List relations); + + // Using the whole person object + @Query("" + + "UNWIND $person.__properties__.KNOWS As rel WITH rel " + + "MATCH (f:Person {id: $person.__id__}) " + + "MATCH (t:Language {name: rel.__target__.__id__}) " + + "CREATE (f) - [r:KNOWS {description: rel.__properties__.description}] -> (t) " + + "RETURN f, collect(r), collect(t)") + Person updateRel2(@Param("person") Person person); } @Service @@ -127,6 +174,21 @@ class GH2323IT { .collect(Collectors.toList()); return personRepository.updateRel(from, knownLanguages); } + + public Optional updateRel2(String id, List languageNames) { + + Optional original = personRepository.findById(id); + if (original.isPresent()) { + Person person = original.get(); + List knownLanguages = languageNames.stream().map(Language::new) + .map(language -> new Knows("Some description", language)) + .collect(Collectors.toList()); + person.setKnownLanguages(knownLanguages); + return Optional.of(personRepository.updateRel2(person)); + } + + return original; + } } @Configuration diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/Person.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/Person.java index 5db4b3659..9164eef5b 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/Person.java +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/Person.java @@ -61,4 +61,8 @@ public class Person { public KnowsMtEntity getMotherTongue() { return motherTongue; } + + public void setKnownLanguages(List knownLanguages) { + this.knownLanguages = knownLanguages; + } } diff --git a/src/test/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriterTest.java b/src/test/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriterTest.java index daf407af0..894e7f185 100644 --- a/src/test/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriterTest.java +++ b/src/test/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriterTest.java @@ -145,10 +145,10 @@ class Neo4jNestedMapEntityWriterTest { Map properties = (Map) result.get("__properties__"); assertThat(properties).containsEntry("description", Values.value("Some description")); - assertThat(properties).hasEntrySatisfying("__target__", isAMapValue); - properties = properties.get("__target__").asMap(Function.identity()); - assertThat(properties).containsEntry("__id__", Values.value("German")); - assertThat(properties).hasEntrySatisfying("__properties__", isAMapValue); + assertThat(result).hasEntrySatisfying("__target__", isAMapValue); + Map target = ((Value) result.get("__target__")).asMap(Function.identity()); + assertThat(target).containsEntry("__id__", Values.value("German")); + assertThat(target).hasEntrySatisfying("__properties__", isAMapValue); } @Test // DATAGRAPH-1452