From 802b5564c4d48dfbf90d4df94038b102dd095f4b Mon Sep 17 00:00:00 2001 From: "Michael J. Simons" Date: Mon, 6 Aug 2018 16:54:14 +0200 Subject: [PATCH] #386 - Add integration test for deep nested derived queries support. SDN supports derived queries over several, deeply nested properties in the following version combinations: - Spring Data Kay SR9 + Neo4j-OGM 3.0.4 - Spring Data Lovelace RC1 + Neo4j-OGM 3.1.1 As of writing, Spring Boot 2.0.4 picks up Spring Data Kay SR9 + Neo4j-OGM 3.1.0. This is an unsupported combination, see https://github.com/spring-projects/spring-boot/issues/13999. This tests asserts the correct versions for Spring Boot 2.1.0+ and 2.0.5+. Also fixes the use of final field relationships in the domain classes used in the test and removed the usage of deprecated API. --- .../java/example/springdata/neo4j/Actor.java | 16 +++-- .../springdata/neo4j/ActorRepository.java | 15 ++++- .../java/example/springdata/neo4j/Movie.java | 16 +++-- .../java/example/springdata/neo4j/Role.java | 20 +++--- .../neo4j/ActorRepositoryIntegrationTest.java | 62 +++++++++++++++++-- 5 files changed, 102 insertions(+), 27 deletions(-) diff --git a/neo4j/example/src/main/java/example/springdata/neo4j/Actor.java b/neo4j/example/src/main/java/example/springdata/neo4j/Actor.java index ab787e65..1f714dea 100644 --- a/neo4j/example/src/main/java/example/springdata/neo4j/Actor.java +++ b/neo4j/example/src/main/java/example/springdata/neo4j/Actor.java @@ -19,12 +19,12 @@ package example.springdata.neo4j; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; import java.util.HashSet; import java.util.Set; -import org.neo4j.ogm.annotation.GraphId; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; @@ -33,16 +33,20 @@ import org.neo4j.ogm.annotation.Relationship; * * @author Luanne Misquitta * @author Oliver Gierke + * @author Michael J. Simons */ @NodeEntity(label = "Actor") @NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@RequiredArgsConstructor @Getter public class Actor { - private @GraphId Long id; - private final String name; - private final @Relationship(type = "ACTED_IN") Set roles = new HashSet<>(); + private @Id @GeneratedValue Long id; + private String name; + private @Relationship(type = "ACTED_IN") Set roles = new HashSet<>(); + + public Actor(String name) { + this.name = name; + } public void actedIn(Movie movie, String roleName) { diff --git a/neo4j/example/src/main/java/example/springdata/neo4j/ActorRepository.java b/neo4j/example/src/main/java/example/springdata/neo4j/ActorRepository.java index 2fa9606a..aa87f273 100644 --- a/neo4j/example/src/main/java/example/springdata/neo4j/ActorRepository.java +++ b/neo4j/example/src/main/java/example/springdata/neo4j/ActorRepository.java @@ -15,11 +15,22 @@ */ package example.springdata.neo4j; +import java.util.List; + import org.springframework.data.neo4j.repository.Neo4jRepository; /** - * {@link GraphRepository} for {@link Actor}s. + * {@link Neo4jRepository} for {@link Actor actors}. * * @author Luanne Misquitta + * @author Michael J. Simons */ -public interface ActorRepository extends Neo4jRepository {} +public interface ActorRepository extends Neo4jRepository { + /** + * Nested property from select from roles -> movie -> title, + * where this here represents the start node in a relationship and movie the end node. + * @param title + * @return + */ + List findAllByRolesMovieTitle(String title); +} diff --git a/neo4j/example/src/main/java/example/springdata/neo4j/Movie.java b/neo4j/example/src/main/java/example/springdata/neo4j/Movie.java index a202367c..523a4836 100644 --- a/neo4j/example/src/main/java/example/springdata/neo4j/Movie.java +++ b/neo4j/example/src/main/java/example/springdata/neo4j/Movie.java @@ -19,12 +19,12 @@ package example.springdata.neo4j; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; import java.util.HashSet; import java.util.Set; -import org.neo4j.ogm.annotation.GraphId; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Relationship; @@ -33,14 +33,18 @@ import org.neo4j.ogm.annotation.Relationship; * * @author Luanne Misquitta * @author Oliver Gierke + * @author Michael J. Simons */ @NodeEntity(label = "Movie") @NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@RequiredArgsConstructor @Getter public class Movie { - private @GraphId Long id; - private final String title; - private final @Relationship(type = "ACTED_IN", direction = "INCOMING") Set roles = new HashSet<>(); + private @Id @GeneratedValue Long id; + private String title; + private @Relationship(type = "ACTED_IN", direction = "INCOMING") Set roles = new HashSet<>(); + + public Movie(String title) { + this.title = title; + } } diff --git a/neo4j/example/src/main/java/example/springdata/neo4j/Role.java b/neo4j/example/src/main/java/example/springdata/neo4j/Role.java index 102b873c..3882cace 100644 --- a/neo4j/example/src/main/java/example/springdata/neo4j/Role.java +++ b/neo4j/example/src/main/java/example/springdata/neo4j/Role.java @@ -18,10 +18,10 @@ package example.springdata.neo4j; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; import org.neo4j.ogm.annotation.EndNode; -import org.neo4j.ogm.annotation.GraphId; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.RelationshipEntity; import org.neo4j.ogm.annotation.StartNode; @@ -29,15 +29,21 @@ import org.neo4j.ogm.annotation.StartNode; * A Role relationship entity between an actor and movie. * * @author Luanne Misquitta + * @author Michael J. Simons */ @RelationshipEntity(type = "ACTED_IN") @NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@RequiredArgsConstructor @Getter public class Role { - private @GraphId Long id; - private final @StartNode Actor actor; - private final String role; - private final @EndNode Movie movie; + private @Id @GeneratedValue Long id; + private @StartNode Actor actor; + private String role; + private @EndNode Movie movie; + + Role(Actor actor, String role, Movie movie) { + this.actor = actor; + this.role = role; + this.movie = movie; + } } diff --git a/neo4j/example/src/test/java/example/springdata/neo4j/ActorRepositoryIntegrationTest.java b/neo4j/example/src/test/java/example/springdata/neo4j/ActorRepositoryIntegrationTest.java index b0d537ed..ac63790a 100644 --- a/neo4j/example/src/test/java/example/springdata/neo4j/ActorRepositoryIntegrationTest.java +++ b/neo4j/example/src/test/java/example/springdata/neo4j/ActorRepositoryIntegrationTest.java @@ -16,20 +16,27 @@ package example.springdata.neo4j; import static org.assertj.core.api.Assertions.*; +import static org.junit.Assume.*; + +import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootVersion; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.util.Version; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ClassUtils; /** * Simple integration test demonstrating the use of the ActorRepository * * @author Luanne Misquitta * @author Oliver Gierke + * @author Michael J. Simons */ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest @@ -37,14 +44,12 @@ import org.springframework.transaction.annotation.Transactional; public class ActorRepositoryIntegrationTest { @SpringBootApplication - static class ExampleConfig {} + static class ExampleConfig { + } @Autowired ActorRepository actorRepository; - /** - * @see #131 - */ - @Test + @Test // #131 public void shouldBeAbleToSaveAndLoadActor() { Movie goblet = new Movie("Harry Potter and the Goblet of Fire"); @@ -57,7 +62,52 @@ public class ActorRepositoryIntegrationTest { assertThat(actorRepository.findById(daniel.getId())).hasValueSatisfying(actor -> { assertThat(actor.getName()).isEqualTo(daniel.getName()); assertThat(actor.getRoles()).hasSize(1).first() - .satisfies(role -> assertThat(role.getRole()).isEqualTo("Harry Potter")); + .satisfies(role -> assertThat(role.getRole()).isEqualTo("Harry Potter")); }); } + + @Test // #386 + public void shouldBeAbleToHandleNestedProperties() { + + assumeTrue(thatSupportForNestedPropertiesIsAvailable()); + + Movie theParentTrap = new Movie("The Parent Trap"); + Movie iKnowWhoKilledMe = new Movie("I Know Who Killed Me"); + + Actor lindsayLohan = new Actor("Lindsay Lohan"); + + lindsayLohan.actedIn(theParentTrap, "Hallie Parker"); + lindsayLohan.actedIn(theParentTrap, "Annie James"); + lindsayLohan.actedIn(iKnowWhoKilledMe, "Aubrey Fleming"); + lindsayLohan.actedIn(iKnowWhoKilledMe, "Dakota Moss"); + actorRepository.save(lindsayLohan); + + Actor nealMcDonough = new Actor("Neal McDonough"); + nealMcDonough.actedIn(iKnowWhoKilledMe, "Daniel Fleming"); + actorRepository.save(nealMcDonough); + + assertThat(actorRepository.findAllByRolesMovieTitle(iKnowWhoKilledMe.getTitle())).hasSize(2) + .extracting(Actor::getName).contains(lindsayLohan.getName(), nealMcDonough.getName()); + } + + private static boolean thatSupportForNestedPropertiesIsAvailable() { + + Version minVersion = Version.parse("2.0.5"); + Optional currentSpringBootVersion = Optional.ofNullable(SpringBootVersion.getVersion()); + + return currentSpringBootVersion.map(Version::parse) + .map(v -> v.isGreaterThanOrEqualTo(minVersion)) + .orElseGet(ActorRepositoryIntegrationTest::fallBackToVersionSpecificClasses); + } + + private static boolean fallBackToVersionSpecificClasses() { + + ClassLoader usedClassLoader = ActorRepositoryIntegrationTest.class.getClassLoader(); + + String fqnBoot210Class = "org.springframework.boot.autoconfigure.insight.InsightsProperties"; + String fqnBoot205Class = "org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider"; + + return ClassUtils.isPresent(fqnBoot210Class, usedClassLoader) || ClassUtils + .isPresent(fqnBoot205Class, usedClassLoader); + } }