2
votes

We are using spring-data-neo4j with repository methods and annotated queries. Formerly, we used SDN with a rest connection to a standalone server. Performance was very poor, though, so we decided to use SDN with an embedded neo4j instance. But that does not work as expected.

Here are some classes

The node entity

import java.util.Set;

import org.codehaus.jackson.annotate.JsonIgnore;
import org.neo4j.graphdb.Direction;
import org.springframework.data.neo4j.annotation.Fetch;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelatedToVia;

import com.xxx.xyz.relationships.UserToAnnotation;
import com.xxx.xyz.relationships.UserToBookmark;
import com.xxx.xyz.relationships.UserToGroup;
import com.xxx.xyz.relationships.UserToLastReadMedia;
import com.xxx.xyz.relationships.UserToLicenseOwner;
import com.xxx.xyz.relationships.UserToLicenseReader;
import com.xxx.xyz.relationships.UserToUser;

/** Simple user class. */
@NodeEntity
public class Neo4jUser {

    @GraphId
    Long id;

    private String type;

    @Indexed(unique=true)
    private String emailAddress;

    @Indexed
    private String nickname;

    private String passwordHash;

    private String givenName;

    private String familyName;

    private Long birthdate;


    @JsonIgnore
    @RelatedToVia(type = "isIn", direction = Direction.OUTGOING)
    private Set<UserToGroup> memberships;

    public Neo4jUser() {
    }

    //constructor

    //getter / setter


    public void isIn(final Neo4jGroup group, final String role) {
        memberships.add(new UserToGroup(this, group, role));
    }



}

One relationship entity

package com.xxx.xyz.relationships;

import org.springframework.data.neo4j.annotation.EndNode;
import org.springframework.data.neo4j.annotation.Fetch;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
import org.springframework.data.neo4j.annotation.StartNode;

import com.xxx.xyz.model.db.Neo4jGroup;
import com.xxx.xyz.model.db.Neo4jUser;

@RelationshipEntity(type="isIn")
public class UserToGroup {

    @GraphId
    Long id;

    @Fetch @StartNode
    private Neo4jUser user;

    @Fetch @EndNode
    private Neo4jGroup group;

    private String role;

    public UserToGroup(){
    }

    public UserToGroup(final Neo4jUser user, final Neo4jGroup group, final String role)
    {
        this.user = user;
        this.group = group;
        this.role = role;
    }

}

The user repository

@Transactional
public interface UserRepository extends GraphRepository<Neo4jUser> {

//annotated cypher queries

}

spring applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/data/neo4j 
        http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:spring-configured />
    <context:component-scan base-package="com.xxx.xyz.service" />

    <neo4j:config storeDirectory="data/graph.db" />

    <neo4j:repositories base-package="com.xxx.xyz.repository" />


</beans>

maven dependency

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-neo4j</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

When trying to modify the set memberships by using the method Neo4JUser.isIn() the following exception is thrown

org.neo4j.graphdb.NotInTransactionException
    at org.neo4j.kernel.impl.persistence.PersistenceManager.getResource(PersistenceManager.java:252)
    at org.neo4j.kernel.impl.persistence.PersistenceManager.relationshipCreate(PersistenceManager.java:161)
    at org.neo4j.kernel.impl.core.NodeManager.createRelationship(NodeManager.java:252)
    at org.neo4j.kernel.impl.core.NodeImpl.createRelationshipTo(NodeImpl.java:578)
    at org.neo4j.kernel.impl.core.NodeProxy.createRelationshipTo(NodeProxy.java:207)
    at org.springframework.data.neo4j.support.DelegatingGraphDatabase.createRelationship(DelegatingGraphDatabase.java:125)
    at org.springframework.data.neo4j.support.mapping.EntityStateHandler.getOrCreateRelationship(EntityStateHandler.java:168)
    at org.springframework.data.neo4j.support.mapping.EntityStateHandler.useOrCreateState(EntityStateHandler.java:139)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.write(Neo4jEntityConverterImpl.java:146)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister$CachedConverter.write(Neo4jEntityPersister.java:179)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:249)
    at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:231)
    at org.springframework.data.neo4j.support.Neo4jTemplate.save(Neo4jTemplate.java:293)
    at org.springframework.data.neo4j.fieldaccess.RelatedToViaCollectionFieldAccessorFactory$RelatedToViaCollectionFieldAccessor.persistEntities(RelatedToViaCollectionFieldAccessorFactory.java:99)
    at org.springframework.data.neo4j.fieldaccess.RelatedToViaCollectionFieldAccessorFactory$RelatedToViaCollectionFieldAccessor.setValue(RelatedToViaCollectionFieldAccessorFactory.java:93)
    at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.updateValue(ManagedFieldAccessorSet.java:94)
    at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.update(ManagedFieldAccessorSet.java:82)
    at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.add(ManagedFieldAccessorSet.java:108)
    at com.xxx.xyz.model.db.Neo4jUser.isIn(Neo4jUser.java:187)

As I found out doing some research, the rest connection of SDN does not use transactions, the embedded version does, so NotInTransactionException are possible.

Moreover, we didn't expect save() to be triggered, when manipulating a set with relationships. We always called save() manually after. Why is that?

But what is done wrong here?

Any help is appreciated!

Thanks!

1

1 Answers

1
votes

this is indeed a bit confusing and I have had the same problems. If you are using the sets for relationships in you model, the set will be backed by SDN. So if you make any modifications to the set, SDN will try to change your DB accoringly(ie. creating/deleting relationships). If you dont want your model to be backed by the database you should use Iterable.

http://static.springsource.org/spring-data/neo4j/docs/current/reference/htmlsingle/#reference:programming_model:relationships:relatedto