From f1ffd424f75239d15583f2ad3748effbb58312c7 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Fri, 14 Aug 2020 15:45:27 +0200 Subject: [PATCH] #582 - Adopt to changes in Spring Data Neo4j 6.0. --- neo4j/example/pom.xml | 15 +--- .../java/example/springdata/neo4j/Actor.java | 51 +++++++---- .../springdata/neo4j/ActorRepository.java | 5 +- .../java/example/springdata/neo4j/Movie.java | 51 +++++++---- .../neo4j/{Role.java => Roles.java} | 47 +++++----- .../neo4j/ActorRepositoryIntegrationTest.java | 89 ++++++++++--------- pom.xml | 2 +- 7 files changed, 148 insertions(+), 112 deletions(-) rename neo4j/example/src/main/java/example/springdata/neo4j/{Role.java => Roles.java} (50%) diff --git a/neo4j/example/pom.xml b/neo4j/example/pom.xml index 8bfed210..b5fb8b58 100644 --- a/neo4j/example/pom.xml +++ b/neo4j/example/pom.xml @@ -21,17 +21,10 @@ - org.neo4j - neo4j-ogm-embedded-driver - ${neo4j-ogm.version} - runtime - - - - org.neo4j - neo4j - 3.1.0 - runtime + org.neo4j.test + neo4j-harness + 3.5.18 + test 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 3d301ea5..1cfa73c6 100644 --- a/neo4j/example/src/main/java/example/springdata/neo4j/Actor.java +++ b/neo4j/example/src/main/java/example/springdata/neo4j/Actor.java @@ -16,17 +16,13 @@ package example.springdata.neo4j; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import java.util.HashMap; +import java.util.Map; -import java.util.HashSet; -import java.util.Set; - -import org.neo4j.ogm.annotation.GeneratedValue; -import org.neo4j.ogm.annotation.Id; -import org.neo4j.ogm.annotation.NodeEntity; -import org.neo4j.ogm.annotation.Relationship; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; /** * An Actor node entity. @@ -35,14 +31,12 @@ import org.neo4j.ogm.annotation.Relationship; * @author Oliver Gierke * @author Michael J. Simons */ -@NodeEntity(label = "Actor") -@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@Getter +@Node public class Actor { private @Id @GeneratedValue Long id; - private String name; - private @Relationship(type = "ACTED_IN") Set roles = new HashSet<>(); + private final String name; + private @Relationship(type = "ACTED_IN") Map roles = new HashMap<>(); public Actor(String name) { this.name = name; @@ -50,9 +44,30 @@ public class Actor { public void actedIn(Movie movie, String roleName) { - Role role = new Role(this, roleName, movie); + Roles movieRoles = this.roles.computeIfAbsent(movie, m -> new Roles()); + movieRoles.addRole(roleName); + movie.getActors().put(this, movieRoles); + } - roles.add(role); - movie.getRoles().add(role); + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Map getRoles() { + return roles; + } + + public void setRoles(Map roles) { + this.roles = roles; + } + + @Override public String toString() { + return "Actor{" + + "name='" + name + '\'' + + '}'; } } 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 053a0c08..6c9a17de 100644 --- a/neo4j/example/src/main/java/example/springdata/neo4j/ActorRepository.java +++ b/neo4j/example/src/main/java/example/springdata/neo4j/ActorRepository.java @@ -18,6 +18,7 @@ package example.springdata.neo4j; import java.util.List; import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; /** * {@link Neo4jRepository} for {@link Actor actors}. @@ -28,11 +29,11 @@ import org.springframework.data.neo4j.repository.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. + * Custom query to navigate over relationships with properties. * * @param title * @return */ + @Query("MATCH (a:Actor) - [r:ACTED_IN] -> (m:Movie {title: $title}) RETURN a, collect(r), collect(m)") 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 588026c5..71f663b8 100644 --- a/neo4j/example/src/main/java/example/springdata/neo4j/Movie.java +++ b/neo4j/example/src/main/java/example/springdata/neo4j/Movie.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,14 @@ package example.springdata.neo4j; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import java.util.HashMap; +import java.util.Map; -import java.util.HashSet; -import java.util.Set; - -import org.neo4j.ogm.annotation.GeneratedValue; -import org.neo4j.ogm.annotation.Id; -import org.neo4j.ogm.annotation.NodeEntity; -import org.neo4j.ogm.annotation.Relationship; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.schema.Relationship.Direction; /** * A Movie node entity. @@ -35,16 +32,38 @@ import org.neo4j.ogm.annotation.Relationship; * @author Oliver Gierke * @author Michael J. Simons */ -@NodeEntity(label = "Movie") -@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@Getter +@Node public class Movie { private @Id @GeneratedValue Long id; - private String title; - private @Relationship(type = "ACTED_IN", direction = "INCOMING") Set roles = new HashSet<>(); + + private final String title; + private @Relationship(type = "ACTED_IN", direction = Direction.INCOMING) + Map actors = new HashMap<>(); public Movie(String title) { this.title = title; } + + public Long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public Map getActors() { + return actors; + } + + public void setActors(Map actors) { + this.actors = actors; + } + + @Override public String toString() { + return "Movie{" + + "title='" + title + '\'' + + '}'; + } } diff --git a/neo4j/example/src/main/java/example/springdata/neo4j/Role.java b/neo4j/example/src/main/java/example/springdata/neo4j/Roles.java similarity index 50% rename from neo4j/example/src/main/java/example/springdata/neo4j/Role.java rename to neo4j/example/src/main/java/example/springdata/neo4j/Roles.java index eabcf7ba..b5cc0c00 100644 --- a/neo4j/example/src/main/java/example/springdata/neo4j/Role.java +++ b/neo4j/example/src/main/java/example/springdata/neo4j/Roles.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,12 @@ */ package example.springdata.neo4j; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; -import org.neo4j.ogm.annotation.EndNode; -import org.neo4j.ogm.annotation.GeneratedValue; -import org.neo4j.ogm.annotation.Id; -import org.neo4j.ogm.annotation.RelationshipEntity; -import org.neo4j.ogm.annotation.StartNode; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; /** * A Role relationship entity between an actor and movie. @@ -31,19 +28,27 @@ import org.neo4j.ogm.annotation.StartNode; * @author Luanne Misquitta * @author Michael J. Simons */ -@RelationshipEntity(type = "ACTED_IN") -@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@Getter -public class Role { +@RelationshipProperties +public class Roles { - private @Id @GeneratedValue Long id; - private @StartNode Actor actor; - private String role; - private @EndNode Movie movie; + private final List roles; - Role(Actor actor, String role, Movie movie) { - this.actor = actor; - this.role = role; - this.movie = movie; + public Roles() { + this(Collections.emptyList()); + } + + @PersistenceConstructor + public Roles(List roles) { + this.roles = new ArrayList<>(roles); + } + + public List getRoles() { + return roles; + } + + public void addRole(String role) { + if (!this.roles.contains(role)) { + roles.add(role); + } } } 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 8be432b0..4fd83c04 100644 --- a/neo4j/example/src/test/java/example/springdata/neo4j/ActorRepositoryIntegrationTest.java +++ b/neo4j/example/src/test/java/example/springdata/neo4j/ActorRepositoryIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,21 +15,17 @@ */ package example.springdata.neo4j; -import static org.assertj.core.api.Assertions.*; -import static org.junit.Assume.*; +import static org.assertj.core.api.Assertions.assertThat; -import java.util.Optional; - -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.neo4j.harness.ServerControls; +import org.neo4j.harness.TestServerBuilders; 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.SpringRunner; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.ClassUtils; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; /** * Simple integration test demonstrating the use of the ActorRepository @@ -38,18 +34,36 @@ import org.springframework.util.ClassUtils; * @author Oliver Gierke * @author Michael J. Simons */ -@RunWith(SpringRunner.class) @SpringBootTest -@Transactional -public class ActorRepositoryIntegrationTest { +class ActorRepositoryIntegrationTest { + + private static final ServerControls neo4j = TestServerBuilders + .newInProcessBuilder().newServer(); + + @AfterAll + static void stopNeo4j() { + + if (neo4j != null) { + neo4j.close(); + } + } + + @DynamicPropertySource + static void neo4jProperties(DynamicPropertyRegistry registry) { + + registry.add("spring.neo4j.uri", neo4j::boltURI); + registry.add("spring.neo4j.authentication.username", () -> "neo4j"); + registry.add("spring.neo4j.authentication.password", () -> "password"); + } @SpringBootApplication - static class ExampleConfig {} + static class ExampleConfig { + } @Autowired ActorRepository actorRepository; @Test // #131 - public void shouldBeAbleToSaveAndLoadActor() { + void shouldBeAbleToSaveAndLoadActor() { Movie goblet = new Movie("Harry Potter and the Goblet of Fire"); @@ -60,15 +74,14 @@ 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")); + assertThat(actor.getRoles()).hasSize(1) + .satisfies(roles -> assertThat(roles.values()).flatExtracting(Roles::getRoles).hasSize(1).first() + .isEqualTo("Harry Potter")); }); } @Test // #386 - public void shouldBeAbleToHandleNestedProperties() { - - assumeTrue(thatSupportForNestedPropertiesIsAvailable()); + void shouldBeAbleToHandleNestedProperties() { Movie theParentTrap = new Movie("The Parent Trap"); Movie iKnowWhoKilledMe = new Movie("I Know Who Killed Me"); @@ -86,26 +99,16 @@ public class ActorRepositoryIntegrationTest { 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"); - - return Optional.ofNullable(SpringBootVersion.getVersion()).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); + .satisfies(actors -> assertThat(actors).extracting(Actor::getName) + .contains(lindsayLohan.getName(), nealMcDonough.getName())) + .allSatisfy(actor -> { + if (actor.getName().equals(nealMcDonough.getName())) { + assertThat(actor.getRoles()) + .allSatisfy((m, r) -> assertThat(r.getRoles()).containsOnly("Daniel Fleming")); + } else if (actor.getName().equals(lindsayLohan.getName())) { + assertThat(actor.getRoles()) + .allSatisfy((m, r) -> assertThat(r.getRoles()).containsOnly("Aubrey Fleming", "Dakota Moss")); + } + }); } } diff --git a/pom.xml b/pom.xml index a90660b8..f6c404ce 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ map mongodb multi-store - + neo4j r2dbc rest redis