Reactive (or regular) Spring Data Neo4j keeps around relationships - neo4j

I am trying to assign/save a new list with a single node and expecting it to be in the same state after the save.
Yet, if I return the Foo from Neo4j and reassign the list of Bars, it maintains a list of all Bars that were previously added.
I would expect that previous Bar nodes in Neo4j are orphaned and the Foo only has a single relationship at any given time.
How can I tell Spring Data for Neo4j to not "merge" (or whatever you call it) the list and reset the relationships exactly as the list is passed in (with one node).
Any thoughts, advice, or guidance would be greatly appreciated! Thanks!
Below, you can find a straightforward application that reproduces this problem.
#Component
public class RelationshipCommandLineListener implements CommandLineRunner {
#Autowired
private FooRepository fooRepository;
#Override
public void run(String... args) throws Exception {
fooRepository.findById("foo").defaultIfEmpty(new Foo("foo", Collections.emptyList()))
.map(foo -> {
foo.setBars(List.of(new Bar(System.currentTimeMillis())));
return foo;
})
.flatMap(foo -> fooRepository.save(foo))
.block();
}
}
#SpringBootApplication
public class Main {
#Bean(ReactiveNeo4jRepositoryConfigurationExtension.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)
public ReactiveTransactionManager reactiveTransactionManager(Driver driver, ReactiveDatabaseSelectionProvider databaseNameProvider) {
return new ReactiveNeo4jTransactionManager(driver, databaseNameProvider);
}
public static void main(String[] args) {
new SpringApplicationBuilder()
.web(WebApplicationType.NONE)
.sources(Main.class)
.build()
.run(args);
}
}
Foo, simple holds up to a list of Bars.
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Node(primaryLabel = "Foo")
public class Foo {
#Id
String id;
private List<Bar> bars = new ArrayList<>();
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Node(primaryLabel = "Bar")
public class Bar {
#Id
String id;
}

You need to define a relationship in your model Foo. Try this:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Node(primaryLabel = "Foo")
public class Foo {
#Id
String id;
#Relationship(type = "HAS_BARS", direction = OUTGOING)
private List<Bar> bars = new ArrayList<>();
}

Related

Neo4j Spring Data NodeEntity use String as #id

I'm trying to use a java.lang.String as the #Id of a NodeEntity.
#NodeEntity(label = "MachineType")
public class MachineType {
#Id private String id;
....
It should be possible acording to the spring data neo4j docu:
While an id is still required on all entities, the behavior has been
simplified by introducing the new #Id annotation. It replaces both
#GraphId and the primary attribute and can be placed on any attribute
with a simple type.
When I'm trying to insert I get a:
{
"cause": null,
"message": "Id must be assignable to Serializable!: null"
}
Which is strange, because String implements the Serializable.
Anyone has an idea where to search next?
I think that you cannot use anything else as an ID. Keep in mind that this Long number will be reused if you delete the node.
I use UUID plugin to generate true unique keys and when I use spring-data-rest I use BackendIdConverter to change the id to the uuid for the resources that I expose.
Example:
Model:
#NodeEntity
#Data
public class Target {
#Id #GeneratedValue Long id; // <----Neo4j id
private String uuid; // <----My Key
#Version Long version;
private List<String> labels = new ArrayList<>();
#Relationship(type = "HAS_MEDIA", direction=Relationship.OUTGOING)
private List<Gallery> media = new ArrayList<>();
}
Convert resource id to my key:
#Component
public class MovieIdConverter implements BackendIdConverter {
#Autowired MovieRepo movieRepository;
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
Movie movie = movieRepository.findByUuid(id);
return (Serializable) movie.getId();
}
#Override
public String toRequestId(Serializable serializable, Class<?> aClass) {
Long id = (Long) serializable;
Optional<Movie> movie = movieRepository.findById(id);
if (movie.isPresent()) return movie.get().getUuid();
return null;
}
#Override
public boolean supports(Class<?> aClass) {
return Movie.class.equals(aClass);
}
}

Spring Data Neo4j and InvalidDataAccessApiUsageException for #Index property

I have added uuid property to my SDN 4 Base entity so now it looks like:
#NodeEntity
public abstract class BaseEntity {
#GraphId
private Long id;
#Index(unique = true, primary = true)
private String uuid;
...
}
Right now some of my tests stopped working.
I have a Commentable abstract class:
#NodeEntity
public abstract class Commentable extends BaseEntity {
private final static String COMMENTED_ON = "COMMENTED_ON";
#Relationship(type = COMMENTED_ON, direction = Relationship.INCOMING)
private Set<Comment> comments = new HashSet<>();
public Set<Comment> getComments() {
return comments;
}
public void addComment(Comment comment) {
if (comment != null) {
comments.add(comment);
}
}
public void removeComment(Comment comment) {
comments.remove(comment);
}
}
Different NodeEntity from my domain model extend this class.. like
public class Decision extends Commentable
public class Product extends Commentable
etc
This my SDN 4 repository
#Repository
public interface CommentableRepository extends GraphRepository<Commentable> {
}
Right now when I try to access
commentableRepository.findOne(commentableId);
where commentableId is Decision id or Product id it is fails with a following exception:
org.springframework.dao.InvalidDataAccessApiUsageException: Supplied id does not match primary index type on supplied class.; nested exception is org.neo4j.ogm.session.Neo4jException: Supplied id does not match primary index type on supplied class.
at org.springframework.data.neo4j.transaction.SessionFactoryUtils.convertOgmAccessException(SessionFactoryUtils.java:154)
at org.springframework.data.neo4j.repository.support.SessionBeanDefinitionRegistrarPostProcessor.translateExceptionIfPossible(SessionBeanDefinitionRegistrarPostProcessor.java:71)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy175.findOne(Unknown Source)
But if I remove #Index(unique = true, primary = true) from my BaseEntity.uuid field everything starts working fine.
How to solve this issue ?
You are using the wrong Repository. GraphRepository is a legacy implementation. The new implementation is called Neo4jRepository.
Try:
public interface CommentableRepository extends Neo4jRepository<Commentable, String> {
...
}
The key difference is the second parameterized type which is the type of the ID used for the domain class; in this case String uuid.

spring data neo4j #Query by relationship type

I need to query neo4j by relationship type
this is My Entity class
#NodeEntity
#Getter
#Setter
#NoArgsConstructor
public class ProductRecommendation {
#GraphId
private Long id;
String product;
#Relationship(type = "RECOMMENDS", direction = Relationship.OUTGOING)
Set<ProductRecommendation> linkedProducts = new HashSet<>();
}
I need to find all the nodes with relationship type as "RECOMMENDS".
Is there a default findBy method?
I tried with this and it works
public interface ProductRecommendationRepository extends GraphRepository<ProductRecommendation> {
#Query("MATCH p=()-[r:RECOMMENDS]->() RETURN p")
List<ProductRecommendation> findByRelationShipType();
}
however if I pass relationship type as variable, it doesn't work
public interface ProductRecommendationRepository extends GraphRepository<ProductRecommendation> {
#Query("MATCH p=()-[r:{0}]->() RETURN p")
List<ProductRecommendation> findByRelationShipType(String type);
}
Can someone please explain.
Thanks
The relationship type cannot be parameterised (see http://neo4j.com/docs/developer-manual/current/cypher/#cypher-parameters).
so you will have to go with
public interface ProductRecommendationRepository extends GraphRepository<ProductRecommendation> {
#Query("MATCH p=()-[r:RECOMMENDS]->() RETURN p")
List<ProductRecommendation> findByRelationShipType();
}

MappingException: Error mapping GraphModel to instance

I'm trying to follow the new Cineasts app with SDN 4.0.0.M1 and SpringBoot to learn Spring and Neo4j but I have an error when I try to access the movie url with
curl http://localhost:8080/movies
MappingException: Error mapping GraphModel to instance
I implemented the minimum to get something working so the code is simple but I probably forgot something
the movie class
#NodeEntity
public class Movie {
#GraphId
private Long nodeId;
private String id;
private String title;
public Movie() {
}
public Movie(String id, String title) {
this.id = id;
this.title = title;
}
}
the associated MovieRepository is empty at the moment
public interface MovieRepository extends GraphRepository<Movie> {
}
the MovieController
#Autowired
private MovieRepository movieRepository;
#Autowired
private Session session;
#RequestMapping(value = "/movies/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
public
#ResponseBody
Movie getMovie(#PathVariable String id) {
return IteratorUtil.firstOrNull(findMovieByProperty("id", id));
}
public Iterable<Movie> findMovieByProperty(String propertyName, Object propertyValue) {
return session.loadByProperty(Movie.class, new Property(propertyName, propertyValue));
}
and the main class with database connection
#SpringBootApplication
#EnableNeo4jRepositories("cineasts.repository")
#EnableTransactionManagement
public class CineastsApplication extends Neo4jConfiguration {
public static final int NEO4J_PORT = 7474;
#Bean
public Neo4jServer neo4jServer() {
return new RemoteServer("http://localhost:" + NEO4J_PORT);
}
#Override
public SessionFactory getSessionFactory() {
return new SessionFactory("org.neo4j.cineasts.domain");
}
#Override
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Session getSession() throws Exception {
return super.getSession();
}
public static void main(String[] args) {
SpringApplication.run(CineastsApplication.class, args);
}
}
I started Neo4j and added one record with Neo4j browser
CREATE (m:Movie {id:1, name:'The Matrix'}) return m
when I go to localhost:8080 I can see the json response
{
"_links" : {
"movies" : {
"href" : "http://localhost:8080/movies"
},
"profile" : {
"href" : "http://localhost:8080/alps"
}
}
but it fails to display the movies or http://localhost:8080/movies/1 record I just created. Any idea to fix this or get a more relevant message?
Thanks!
The problem could be the fact that your entity definition does not match that of the node you've created.
The Movie class defines a property id of data type String, and a property title of type String.
The Cypher you used however
CREATE (m:Movie {id:1, name:'The Matrix'}) return m
creates a node with a number id instead of a String id and a name property instead of a title property.
Changing the above to
CREATE (m:Movie {id:'1', title:'The Matrix'}) return m
should fix it.

Target state is not the requested interface org.neo4j.graphdb.Node but null

I encountered some difficulties during playing with neo4j. Firstly, when I try to delete defined #EntityModel, I get an exception (Please, forgive me the quality of pics, the exception messages are also in question title):
My Controller (this is just for testing purpouse):
#Controller
public class HomeController {
#Autowired
PersonRepository personRepository;
#RequestMapping(value="/", method = RequestMethod.GET)
public String loadPage(final Model model, final HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
Person person = new Person("My person");
personRepository.save(person);
personRepository.findOne(person.getId());
return "home";
}
}
And model:
#NodeEntity
public class Person {
#GraphId
private Long id;
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public Long getId() {
return id;
}
}
Configuration file:
#Configuration
#EnableTransactionManagement
#EnableNeo4jRepositories(basePackages = "com.springapp.mvc.repository")
#ComponentScan({"com.springapp.mvc"})
public class PersistenceConfig extends Neo4jConfiguration {
#Bean
public GraphDatabaseService graphDatabaseService() {
return new SpringRestGraphDatabase("http://localhost:7474/db/data");
}
}
My Repository:
public interface PersonRepository extends GraphRepository<Person> {
#Query("MATCH (n:Person{name: \"{namveValue}\"}) RETURN n;")
Person findByName(#Param("nameValue") final String name);
}
What am I doing wrong? I figured out that perhaps Person should implement org.neo4j.graphdb.Node and this is the source of these exceptions. However, having searched github repos I see that people do not implement this interface in their models. I have not found solution on stackoverflow so far.
Node exists in database but I cannot either delete it or save it. Please, help.
You are trying to see if a node with ID '0' exists as a person. Since the root node hasn't got a '__type__' property, the call will fail. SDN uses this property to determine the entity type of a node.
That being said, the exception seems to be caused by the following line:
if(! personRepository.exists(0L)) {

Resources