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