#582 - Adopt to changes in Spring Data Neo4j 6.0.

This commit is contained in:
Michael Simons
2020-08-14 15:45:27 +02:00
committed by Mark Paluch
parent 740522ea07
commit f1ffd424f7
7 changed files with 148 additions and 112 deletions

View File

@@ -21,17 +21,10 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.neo4j</groupId> <groupId>org.neo4j.test</groupId>
<artifactId>neo4j-ogm-embedded-driver</artifactId> <artifactId>neo4j-harness</artifactId>
<version>${neo4j-ogm.version}</version> <version>3.5.18</version> <!-- We can switch this to 4.x when the examples are build with JDK 11. -->
<scope>runtime</scope> <scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>3.1.0</version>
<scope>runtime</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -16,17 +16,13 @@
package example.springdata.neo4j; package example.springdata.neo4j;
import lombok.AccessLevel; import java.util.HashMap;
import lombok.Getter; import java.util.Map;
import lombok.NoArgsConstructor;
import java.util.HashSet; import org.springframework.data.neo4j.core.schema.GeneratedValue;
import java.util.Set; import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.neo4j.ogm.annotation.GeneratedValue; import org.springframework.data.neo4j.core.schema.Relationship;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;
/** /**
* An Actor node entity. * An Actor node entity.
@@ -35,14 +31,12 @@ import org.neo4j.ogm.annotation.Relationship;
* @author Oliver Gierke * @author Oliver Gierke
* @author Michael J. Simons * @author Michael J. Simons
*/ */
@NodeEntity(label = "Actor") @Node
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@Getter
public class Actor { public class Actor {
private @Id @GeneratedValue Long id; private @Id @GeneratedValue Long id;
private String name; private final String name;
private @Relationship(type = "ACTED_IN") Set<Role> roles = new HashSet<>(); private @Relationship(type = "ACTED_IN") Map<Movie, Roles> roles = new HashMap<>();
public Actor(String name) { public Actor(String name) {
this.name = name; this.name = name;
@@ -50,9 +44,30 @@ public class Actor {
public void actedIn(Movie movie, String roleName) { 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); public Long getId() {
movie.getRoles().add(role); return id;
}
public String getName() {
return name;
}
public Map<Movie, Roles> getRoles() {
return roles;
}
public void setRoles(Map<Movie, Roles> roles) {
this.roles = roles;
}
@Override public String toString() {
return "Actor{" +
"name='" + name + '\'' +
'}';
} }
} }

View File

@@ -18,6 +18,7 @@ package example.springdata.neo4j;
import java.util.List; import java.util.List;
import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
/** /**
* {@link Neo4jRepository} for {@link Actor actors}. * {@link Neo4jRepository} for {@link Actor actors}.
@@ -28,11 +29,11 @@ import org.springframework.data.neo4j.repository.Neo4jRepository;
public interface ActorRepository extends Neo4jRepository<Actor, Long> { public interface ActorRepository extends Neo4jRepository<Actor, Long> {
/** /**
* Nested property from select from roles -> movie -> title, where this here represents the start node in a * Custom query to navigate over relationships with properties.
* relationship and movie the end node.
* *
* @param title * @param title
* @return * @return
*/ */
@Query("MATCH (a:Actor) - [r:ACTED_IN] -> (m:Movie {title: $title}) RETURN a, collect(r), collect(m)")
List<Actor> findAllByRolesMovieTitle(String title); List<Actor> findAllByRolesMovieTitle(String title);
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,17 +16,14 @@
package example.springdata.neo4j; package example.springdata.neo4j;
import lombok.AccessLevel; import java.util.HashMap;
import lombok.Getter; import java.util.Map;
import lombok.NoArgsConstructor;
import java.util.HashSet; import org.springframework.data.neo4j.core.schema.GeneratedValue;
import java.util.Set; import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.neo4j.ogm.annotation.GeneratedValue; import org.springframework.data.neo4j.core.schema.Relationship;
import org.neo4j.ogm.annotation.Id; import org.springframework.data.neo4j.core.schema.Relationship.Direction;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;
/** /**
* A Movie node entity. * A Movie node entity.
@@ -35,16 +32,38 @@ import org.neo4j.ogm.annotation.Relationship;
* @author Oliver Gierke * @author Oliver Gierke
* @author Michael J. Simons * @author Michael J. Simons
*/ */
@NodeEntity(label = "Movie") @Node
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@Getter
public class Movie { public class Movie {
private @Id @GeneratedValue Long id; private @Id @GeneratedValue Long id;
private String title;
private @Relationship(type = "ACTED_IN", direction = "INCOMING") Set<Role> roles = new HashSet<>(); private final String title;
private @Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
Map<Actor, Roles> actors = new HashMap<>();
public Movie(String title) { public Movie(String title) {
this.title = title; this.title = title;
} }
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public Map<Actor, Roles> getActors() {
return actors;
}
public void setActors(Map<Actor, Roles> actors) {
this.actors = actors;
}
@Override public String toString() {
return "Movie{" +
"title='" + title + '\'' +
'}';
}
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,15 +15,12 @@
*/ */
package example.springdata.neo4j; package example.springdata.neo4j;
import lombok.AccessLevel; import java.util.ArrayList;
import lombok.Getter; import java.util.Collections;
import lombok.NoArgsConstructor; import java.util.List;
import org.neo4j.ogm.annotation.EndNode; import org.springframework.data.annotation.PersistenceConstructor;
import org.neo4j.ogm.annotation.GeneratedValue; import org.springframework.data.neo4j.core.schema.RelationshipProperties;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.annotation.StartNode;
/** /**
* A Role relationship entity between an actor and movie. * A Role relationship entity between an actor and movie.
@@ -31,19 +28,27 @@ import org.neo4j.ogm.annotation.StartNode;
* @author Luanne Misquitta * @author Luanne Misquitta
* @author Michael J. Simons * @author Michael J. Simons
*/ */
@RelationshipEntity(type = "ACTED_IN") @RelationshipProperties
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) public class Roles {
@Getter
public class Role {
private @Id @GeneratedValue Long id; private final List<String> roles;
private @StartNode Actor actor;
private String role;
private @EndNode Movie movie;
Role(Actor actor, String role, Movie movie) { public Roles() {
this.actor = actor; this(Collections.emptyList());
this.role = role; }
this.movie = movie;
@PersistenceConstructor
public Roles(List<String> roles) {
this.roles = new ArrayList<>(roles);
}
public List<String> getRoles() {
return roles;
}
public void addRole(String role) {
if (!this.roles.contains(role)) {
roles.add(role);
}
} }
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,21 +15,17 @@
*/ */
package example.springdata.neo4j; package example.springdata.neo4j;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.*;
import java.util.Optional; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.Test; import org.neo4j.harness.ServerControls;
import org.junit.runner.RunWith; import org.neo4j.harness.TestServerBuilders;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootVersion;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.util.Version; import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.DynamicPropertySource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ClassUtils;
/** /**
* Simple integration test demonstrating the use of the ActorRepository * Simple integration test demonstrating the use of the ActorRepository
@@ -38,18 +34,36 @@ import org.springframework.util.ClassUtils;
* @author Oliver Gierke * @author Oliver Gierke
* @author Michael J. Simons * @author Michael J. Simons
*/ */
@RunWith(SpringRunner.class)
@SpringBootTest @SpringBootTest
@Transactional class ActorRepositoryIntegrationTest {
public 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 @SpringBootApplication
static class ExampleConfig {} static class ExampleConfig {
}
@Autowired ActorRepository actorRepository; @Autowired ActorRepository actorRepository;
@Test // #131 @Test // #131
public void shouldBeAbleToSaveAndLoadActor() { void shouldBeAbleToSaveAndLoadActor() {
Movie goblet = new Movie("Harry Potter and the Goblet of Fire"); 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(actorRepository.findById(daniel.getId())).hasValueSatisfying(actor -> {
assertThat(actor.getName()).isEqualTo(daniel.getName()); assertThat(actor.getName()).isEqualTo(daniel.getName());
assertThat(actor.getRoles()).hasSize(1).first() assertThat(actor.getRoles()).hasSize(1)
.satisfies(role -> assertThat(role.getRole()).isEqualTo("Harry Potter")); .satisfies(roles -> assertThat(roles.values()).flatExtracting(Roles::getRoles).hasSize(1).first()
.isEqualTo("Harry Potter"));
}); });
} }
@Test // #386 @Test // #386
public void shouldBeAbleToHandleNestedProperties() { void shouldBeAbleToHandleNestedProperties() {
assumeTrue(thatSupportForNestedPropertiesIsAvailable());
Movie theParentTrap = new Movie("The Parent Trap"); Movie theParentTrap = new Movie("The Parent Trap");
Movie iKnowWhoKilledMe = new Movie("I Know Who Killed Me"); Movie iKnowWhoKilledMe = new Movie("I Know Who Killed Me");
@@ -86,26 +99,16 @@ public class ActorRepositoryIntegrationTest {
actorRepository.save(nealMcDonough); actorRepository.save(nealMcDonough);
assertThat(actorRepository.findAllByRolesMovieTitle(iKnowWhoKilledMe.getTitle())).hasSize(2) assertThat(actorRepository.findAllByRolesMovieTitle(iKnowWhoKilledMe.getTitle())).hasSize(2)
.extracting(Actor::getName).contains(lindsayLohan.getName(), nealMcDonough.getName()); .satisfies(actors -> assertThat(actors).extracting(Actor::getName)
} .contains(lindsayLohan.getName(), nealMcDonough.getName()))
.allSatisfy(actor -> {
private static boolean thatSupportForNestedPropertiesIsAvailable() { if (actor.getName().equals(nealMcDonough.getName())) {
assertThat(actor.getRoles())
Version minVersion = Version.parse("2.0.5"); .allSatisfy((m, r) -> assertThat(r.getRoles()).containsOnly("Daniel Fleming"));
} else if (actor.getName().equals(lindsayLohan.getName())) {
return Optional.ofNullable(SpringBootVersion.getVersion()).map(Version::parse) // assertThat(actor.getRoles())
.map(v -> v.isGreaterThanOrEqualTo(minVersion)) // .allSatisfy((m, r) -> assertThat(r.getRoles()).containsOnly("Aubrey Fleming", "Dakota Moss"));
.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);
} }
} }

View File

@@ -26,7 +26,7 @@
<module>map</module> <module>map</module>
<module>mongodb</module> <module>mongodb</module>
<module>multi-store</module> <module>multi-store</module>
<!-- <module>neo4j</module> --> <module>neo4j</module>
<module>r2dbc</module> <module>r2dbc</module>
<module>rest</module> <module>rest</module>
<module>redis</module> <module>redis</module>