Hot questions for Using Neo4j in relationship

Top Java Programmings / Neo4j / relationship

Question:

I have a tree data structure I'd like to store using Neo4j.

There is a parent node :CodeSet, which is always the root of the tree and a child nodes :Node, which themselves can have child nodes of the same type. They are connected with relationship of type :SUBTREE_OF as follows:

The parent node is displayed in red and it itself has a parent displayed in green.

As soon as parent node and child nodes have some common data, I created an abstract class:

public abstract class AbstractNode {
    private Long id;
    @NotEmpty
    private String code;
    @Relationship(type = "SUBTREE_OF", direction = Relationship.INCOMING)
    private Set<Node> children;

    <getters & setters omitted>
}

Class for the parent node:

public class CodeSet extends AbstractNode {
    @Relationship(type = "SUBTREE_OF", direction = Relationship.OUTGOING)
    private Application parent;

    <getters and setters omitted>
}

Class for the child node:

public class Node extends AbstractNode {
    @NotEmpty
    private String description;
    @NotEmpty
    private String type;
    @NotEmpty
    private String name;
    @NotNull
    @Relationship(type = "SUBTREE_OF", direction = Relationship.OUTGOING)
    private AbstractNode parent;

    <getters and setters omitted>
}

What I need is just making a child node update. I use the following method at my service layer:

public Node update(Node node, Long nodeId) throws EntityNotFoundException {
    Node updated = findById(nodeId, 0);
    updated.setDescription(node.getDescription());
    updated.setType(node.getType());
    updated.setName(node.getName());
    updated.setCode(node.getCode());
    nodeRepository.save(updated);
    return updated;
}

With this I got the following result: The relationship is broken. I also tried out to specify depth=1 at findById method parameter, but that resulted in wrong relationships once again:

After that I tried out modifying bi-directional relationship in my classes to uni-directional so as only one class has an annotated with @Relatinship field pointing to another, but that did not help either.

How to make this work?


Answer:

Was resolved by updating the save operation in the service method:

public Node update(Node node, Long nodeId) throws EntityNotFoundException {
    Node updated = findById(nodeId, 0);
    updated.setDescription(node.getDescription());
    updated.setType(node.getType());
    updated.setName(node.getName());
    updated.setCode(node.getCode());
    //added param depth=0 here
    nodeRepository.save(updated, 0);
    return updated;
}

Question:

Let's suppose we use this simple graph model:

@NodeEntity
class Ad {    
    // GraphId and more Ad attributes...

    @Relationship(type = "HAS_ADVERTISER", direction = Relationship.OUTGOING)
    private User advertiser;

    @Relationship(type = "IS_FAVOURITE_TO", direction = Relationship.OUTGOING)
    private Set<User> favourites = new HashSet<>();

    // Getters and setters
 }

EDIT: Following the advises of one of the aswers, I've tried adding the incoming relationships to User entity, but the problem still remains.

@NodeEntity
class User {
    // User attributes. No incoming relationships for HAS_ADVERTISER neither IS_FAVOURITE_TO...

   // EDIT: Added incoming relationships to User entity, but the problem still remains. Tried annotating getters/setters, but neither works
   @Relationship(type = "HAS_ADVERTISER", direction = Relationship.INCOMING)
   private Set<Ad> ads = new HashSet<>();

   @Relationship(type = "IS_FAVOURITE_TO", direction = Relationship.INCOMING)
   private Set<Ad> favourites = new HashSet<>();

   @Relationship(direction = Relationship.INCOMING)
   public Set<Ad> getAds() {
       return ads;
   }

   @Relationship(direction = Relationship.INCOMING)
   public void setAds(Set<Ad> ads) {
       this.ads = ads;
   }

   @Relationship(direction = Relationship.INCOMING)
   public Set<Ad> getFavourites() {
       return favourites;
   }

   @Relationship(direction = Relationship.INCOMING)
   public void setFavourites(Set<Ad> favourites) {
       this.favourites = favourites;
   }
}

If I execute a cypher query to retrieve the ads with advertiser and favourites information through neo4j console, it's simply working well:

MATCH (ad:Ad),
(ad)-[has_advertiser:HAS_ADVERTISER]->(advertiser:User),
(ad)-[is_favourite_to: IS_FAVOURITE_TO] -> (favouriteUser:User)
return ad, has_advertiser, advertiser, is_favourite_to, favouriteUser

However, if I execute the query through a neo4jRepository instead:

  • advertiser user is persisted correctly.
  • There are two users in favourites set: the advertiser user is always added to the set, which is incorrect because there is no IS_FAVOURITE_TO relationship with this user.

@Repository
public interface AdRepository extends Neo4jRepository<Ad> {

    @Query("MATCH (ad:Ad)," +
            "(ad)-[has_advertiser:HAS_ADVERTISER]->(advertiser:User)," +
            "(ad)-[is_favourite_to: IS_FAVOURITE_TO] -> (favouriteUser:User)" +
            "return ad, has_advertiser, advertiser, " +
            "is_favourite_to, favouriteUser ")
    List<Ad> findAds();
 }

Can I change something in my query or my graph model to avoid this? Maybe a spring-data-neo4j bug?

Versions:

<spring-data-neo4j.version>4.2.0.M1</spring-data-neo4j.version>
<neo4j.ogm.version>2.0.5</neo4j.ogm.version>

Answer:

This is due to the fact that the OGM considers this to be an ambiguous model. If you add the incoming relationships to User, your issue should be resolved.

Update: upon further investigation, this is indeed a bug. Opened https://github.com/neo4j/neo4j-ogm/issues/276

The workaround is to annotate all your getters and setters (and make sure the @Relationship annotation contains both the relationship type and the direction) in both Ad and User.

Question:

EDIT: Sample project available on github.

I'm using Neo4J (Rest graph database, hosted in grapheneDb) and Spring Data in our backend project.

<bean id="graphDatabaseService" class="org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase">

I have a simple one-to-many relationship between two entities: User and Stay.

EDIT: I thought this wasn't relevant for the issue, but after seeing a similar problem in SDN4, I think I need to update the question (there is a basic @NodeEntity class, and both entities are extending this base class).

@NodeEntity
public abstract class BasicNodeEntity implements Serializable {

   @GraphId
   private Long nodeId;
}


public class User extends BasicNodeEntity {

  @RelatedTo(type = "HAS_STAY",  direction = Direction.OUTGOING)
  Set<Stay> stays;

  public void addStay(Stay stay) {
     stays.add(stay);
  }
}

public class Stay extends BasicNodeEntity {

   @RelatedTo(type = "HAS_STAY", direction = Direction.INCOMING)
   User user;
}

I'm unable to persist more than one stay. The first stay I add to the user is persisted correctly, but just the first one. The next stays added never persists, and I always retrieve the first one.

The method I use to create a new stay is:

   @Autowired
   Neo4jOperations template;

   @Transactional
   private void createStay(Stay stay, User user) throws Exception {
      stay = template.save(stay);
      user.addStay(stay);
      template.save(user);
      // If i evaluate user at this point, it contains both stays

      // But if I retrieve the user from the repository, it just contains
      // the first stay, the second one has not persisted.
   }

EDIT: User modified is retrieved correctly through UserRepository.

public interface UserRepositoryCustom {}

public interface UserRepository extends GraphRepository<User>, UserRepositoryCustom {    
   User findById(String id);
}

User user = userRepository.findById(userId);

NOTE: I also tried to save through the repository interface instead of the Neo4jTemplate one, but I have the same problem.

Both entities are correctly saved in the neo4j database, it's just a persistence issue.

I think this should be quite easy, so I'm probably missing something..

Any help would be greatly appreciated.

Relevant versions:

<spring.version>4.0.5.RELEASE</spring.version>
<spring-data-neo4j.version>3.3.2.RELEASE</spring-data-neo4j.version>

There is another SO question with a very similar problem, but without response so far.


Answer:

It is a tricky thing.

Your custom equals method causes two entities which have their node-id set but not yet their uuid-id set, to be equal so that when loading them into a set the set will only contain one.

Code in: RelationshipHelper
protected Set<Object> createEntitySetFromRelationshipEndNodes(Object entity, final MappingPolicy mappingPolicy, final Class<?> relatedType) {
    final Iterable<Node> nodes = getStatesFromEntity(entity);
    final Set<Object> result = new HashSet<Object>();
    for (final Node otherNode : nodes) {
        Object target = template.createEntityFromState(otherNode, relatedType, mappingPolicy);
        result.add(target);
    }
    return result;
}

If you change your code to have an equals/hashcode in your BasicNode entity:

   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof BasicNodeEntity)) return false;

      BasicNodeEntity that = (BasicNodeEntity) o;

      if (nodeId != null) {
         if (!nodeId.equals(that.nodeId)) return false;
      } else {
         if (that.nodeId != null) return false;
      }

      return true;
   }

   @Override
   public int hashCode() {
      return nodeId != null ? nodeId.hashCode() : 0;
   }

so that entities that have only a nodeId set are comparable

and adapt the subclass methods

   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof IdentifiableEntity)) return false;

      IdentifiableEntity entity = (IdentifiableEntity) o;
      //change
      if (!super.equals(o)) return false;

      if (id != null) {
         if (!id.equals(entity.id)) return false;
      } else {
         if (entity.id != null) return false;
      }

      return true;
   }

   @Override
   public int hashCode() {
      //change
      if (super.hashCode() != 0) return super.hashCode();
      return id != null ? id.hashCode() : 0;
   }

Then it works.

Going forward if you are working with Neo4j Server I recommend to you to check out SDN 4 RC2 instead which was released on Friday.

Question:

I am trying to insert the relationships between two nodes in Neo4j. I am using the Neo4J(2.1.8 Community) & spring-data-neo4j(3.3.0.RELEASE).

I am using trying to create the Employee-Manager relationship. This relationship entity is Report. (Both class are given below)

But when I am trying to save the relation ship

Employee manager = new Employee("Dev_manager", "Management");
Employee developer = new Employee("Developer", "Development");
developer.setReportsTo(manager);
developer.relatedTo(manager, "REPORTS_TO")
employeeRepository.save(developer);

I am getting exception as

Exception in thread "main" org.springframework.dao.DataRetrievalFailureException: RELATIONSHIP[0] has no property with propertyKey="type".; nested exception is org.neo4j.graphdb.NotFoundException: RELATIONSHIP[0] has no property with propertyKey="type".

Can any one please help me that what is exactly wrong in this code.

The same code works after I change the type of relations in Employee as

@RelatedToVia(type = "REPORT_TO", elementClass = Report.class, direction = Direction.INCOMING)

Note: I am using this reference for this tutorial.

Employee.java class

@NodeEntity
public class Employee {

@GraphId
private Long id;
private String name;
private String department;

@Fetch
@RelatedTo(type = "REPORTS_TO")
private Employee reportsTo; //Employee reports to some employee (i.e. Manager).

@Fetch
@RelatedTo(type = "REPORTS_TO", direction = Direction.INCOMING)
Set<Employee> directReport; //All the employees who reports to this particular this employee.

@Fetch
@RelatedToVia(type = "REPORTS_TO", elementClass = Report.class, direction = Direction.INCOMING)
Set<Report> relations = new HashSet<Report>(); // All the incoming relationship entities.
//*** Constructor, Getter-setters and other methods...
}

Report.java class

@RelationshipEntity(type = "REPORTS_TO")
public class Report {

@GraphId
private Long id;
private String title;

@Fetch
@StartNode
private Employee child;

@Fetch
@EndNode
private Employee parent;
//*** Constructor, Getter-setters and other methods...
}

**Update: ** I have created 2 relations using this class structure. And I got the below result.

It looks like it creates 2 relations between the node. 1 is empty relation using reportsTo(i.e. REPORTS_TO) and another relation using the relations(i.e. REPORT_TO). Can anyone please update why this is happening?


Answer:

What's the different between: relations and directReport?

I think SDN is just confused with all the duplicate listing of relationships?

Esp. if they are once declared as light relationships without type and once as relationship-entities.

I think for this case it is much clearer and easier to use

template.createRelationshipBetween(employee, manager, "REPORTS_TO");

Or just create, populate and save the relationship-entity Report.

Otherwise you have to make sure that all collections on all sides are consistent with each other.

Question:

I would like to define some relationship type between some typed node. When I look example always they use a String to define relationship type, as in this example . By using:

@RelationshipEntity(type = "ACTED_IN")

I tried to use org.neo4j.graphdb.RelationshipType but RelationshipEntity.type expect a string.

public enum PersonMovieRelationshipType implements RelationshipType {
    ACTED_IN("ACTED_IN"),
    AUTHOR("AUTHOR");

    private String type;

    PersonMovieRelationshipType( String type ){
        this.type = type;
    }

    public String getType() {
        return type;
    }
}

RelationshipType enum provide a method "name()" what to do with ?

I do not like free text way, is it possible use an enum ?

Any full example will be appreciated.

Regards


Answer:

You can't due to the way annotations work. What you could do is declaring the relation names as constants.

interface RelationNames{
  String ACTED_IN = "ACTED_IN";
}

And then use those constants in your code

@RelationshipEntity(type = RelationNames.ACTED_IN)

Question:

I am modelling a very simple use case, using Spring Data Neo4j: I want to have Persons, which are connected by friendship relations. Here is my code (getters and setters are omitted here):

@NodeEntity
public class Person {
    @GraphId
    private Long id;
    private String name;
    private String password;

    @RelatedTo(type = "FRIEND_OF", direction = Direction.BOTH)
    private Set<Person> friends = new HashSet<Person>();

    public Person() {};

    public Person(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public void befriend(Person person) {
        this.friends.add(person);
    }

Ultimately, I make use of the following method in order to make use of my Persons:

@Transactional
private void populateTwoPersons() {
    Person person1 = new Person("Alice", "pw1");
    Person person2 = new Person("Bob", "pw2");

    List<Person> persons = Arrays.asList(person1, person2);
    personRepository.save(persons);

    person1.befriend(person2);

    personRepository.save(persons);
}

In my understanding a friendship relation should be bidirectional, that is why I set its direction to (direction = Direction.BOTH). Now when executing the populateTwoPersons() method it results in the creation of the two person nodes, but not in an edge between them.

Ideas that I have tried are altering the befriend()-function to

public void befriend(Person person) {
    this.friends.add(person);
    person.getFriends().add(this);
}

or setting the direction of the relationship to direction = Direction.OUTGOING. This however creates a directed edge, which is not what I want.

Why don't I get an edge in the first case altogether? Any ideas? :)

Thanks in advance, Manu


Answer:

All Neo4j edges must be directed and they cannot be bi-directional. You can either create the friendship one-way and query it without direction. Or you can create two separate edges between the two friends. I think the latter is more flexible as one person may consider the other a friend but not vice versa.

Question:

I'm writing an application for a guide service and I'm running a 3 server cluster of Neo4j 2.3.1 on digital ocean droplets. I'm also using Spring Data Neo4j 4.0.0.RELEASE from a JSF web app running under Tomcat to write and query my data.

I have a node with potentially 7 different relationships to other nodes that I have successfully saved 80+ instances of but I suddenly started getting Out of memory errors on the instance 1 master neo4j server when I would try to save the node. I updated the memory settings for the Neo4j server to the following :

wrapper.java.initmemory=512
wrapper.java.maxmemory=1024

I no longer get the OutOfMemoryError but now the server fails when I try to save the node. I can query data prior to the server failing fine. I never get an error message in the server 1 master data/log/console.log file but in the Server 2 slave data/log/console.log file I get the following message :

2015-12-31 15:42:00.405-0500 INFO  Instance 1  is alive
2015-12-31 15:42:00.539-0500 INFO  Instance 1  was elected as coordinator
2015-12-31 15:42:00.598-0500 INFO  Instance 1  is available as master at ha://0.0.0.0:6001?serverId=1 with StoreId{creationTime=1447860597504, randomId=2820629596580485150, storeVersion=15250506225055238, upgradeTime=1447860597504, upgradeId=1}
2015-12-31 15:42:00.647-0500 INFO  Instance 1  is available as backup at backup://127.0.0.1:6362 with StoreId{creationTime=1447860597504, randomId=2820629596580485150, storeVersion=15250506225055238, upgradeTime=1447860597504, upgradeId=1}
2015-12-31 15:42:11.652-0500 INFO  Instance 1  has failed

The Node Entity Java code :

public class OutfitterWaterfowlHunt extends WaterfowlHunt {

    @Relationship(type="RUN_BY", direction=Relationship.OUTGOING)
    private GuideService guideService;

    @Relationship(type="GUIDED", direction=Relationship.INCOMING)
    private List<Guide> fieldStaffHunter = new ArrayList<Guide>();

    @Relationship(type="ASSIGNED_BY", direction=Relationship.OUTGOING)
    private Guide fieldStaff;

    public void addGuide(Guide guide)
    {
        this.fieldStaffHunter.add(guide);
    }

    public List<Guide> getFieldStaffHunter() {
        return fieldStaffHunter;
    }

    public void setFieldStaffHunter(List<Guide> fieldStaffHunter) {
        this.fieldStaffHunter = fieldStaffHunter;
    }

    public GuideService getGuideService() {
        return guideService;
    }

    public void setGuideService(GuideService guideService) {
        this.guideService = guideService;
    }

    public Guide getFieldStaff() {
        return fieldStaff;
    }

    public void setFieldStaff(Guide fieldStaff) {
        this.fieldStaff = fieldStaff;
    }   
}



public class WaterfowlHunt extends Excursion {

    @Property(name="type")
    private String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }   
}


public class Excursion extends BaseEntity {

    @Relationship(type="OCCURED_ON", direction=Relationship.OUTGOING)
    private TookPlaceOn occuredOn;

    @Relationship(type="OCCURED_AT", direction=Relationship.OUTGOING)
    private ExcursionLocation excursionLocation;

    @Relationship(type="PARTICIPATED_IN", direction=Relationship.INCOMING)
    private HuntGroupRole participatedIn;

    @Relationship(type="HARVESTED", direction=Relationship.OUTGOING)
    private HuntInformation harvestInfo;


    public TookPlaceOn occuredOn(Day day, Long timeIn, Long timeOut)
    {
        TookPlaceOn tpo = new TookPlaceOn();

        tpo.setDayOfExcursion(day);
        tpo.setExcursion(this);

        tpo.setTimeIn(timeIn);
        tpo.setTimeOut(timeOut);

        this.occuredOn = tpo;

        day.addOccuredOn(tpo);

        return tpo;
    }

    public void occuredAt(ExcursionLocation huntLocation)
    {
        this.setExcursionLocation(huntLocation);
    }

    public void harvested(HuntInformation harvestInfo)
    {
        this.setHarvestInfo(harvestInfo);
    }

    public HuntGroupRole participatedIn(HuntGroup huntGroup, Integer         nbrOfHunters)
    {
        HuntGroupRole hgr = new HuntGroupRole();

        hgr.setHuntGroup(huntGroup);
        hgr.setHuntingSpot(this);

        hgr.setNumberOfHunters(nbrOfHunters);

        this.participatedIn = hgr;

        huntGroup.addParticipatedIn(hgr);

        return hgr;
    }

    public HuntGroupRole getParticipatedIn() {
        return participatedIn;
    }

    public void setParticipatedIn(HuntGroupRole participatedIn) {
        this.participatedIn = participatedIn;
    }

    public TookPlaceOn getOccuredOn() {
        return occuredOn;
    }

    public void setOccuredOn(TookPlaceOn occuredOn) {
        this.occuredOn = occuredOn;
    }

    public ExcursionLocation getExcursionLocation() {
        return excursionLocation;
    }

    public void setExcursionLocation(ExcursionLocation excursionLocation) {
        this.excursionLocation = excursionLocation;
    }

    public HuntInformation getHarvestInfo() {
        return harvestInfo;
    }

    public void setHarvestInfo(HuntInformation harvestInfo) {
        this.harvestInfo = harvestInfo;
    }

}

The code I use to save is through a SDN4 GraphRepository save:

    @Transactional
    public void saveHunt() 
    {        
        OutfitterWaterfowlHunt owh = new OutfitterWaterfowlHunt();

        owh.setType("GuideTypeHere");

        Day doh = this.determineDayOfHunt();

        owh.occuredOn(doh, this.timeIn.getTime(), this.timeOut.getTime());
        owh.participatedIn(this.huntGroup, this.nbrOfHunters);

        GuideService gs = WDSSession.getInstance().getNeo4JController().retrieveGuideServiceByName("GuideServiceNameHere");

        owh.setGuideService(gs);
        owh.setFieldStaffHunter(this.selectedFSMembers);
        owh.setFieldStaff(this.responsibleFieldStaffer);
        owh.occuredAt(this.huntLocation);

        HuntInformation hi = this.populateHuntInformation();

        owh.setHarvestInfo(hi);

        owh = WDSSession.getInstance().getNeo4JController().saveOutfitterWaterfowlHunt(owh);

        StringBuffer msg = new StringBuffer();
        msg.append("Successfully Saved Hunt ");
        msg.append(" (");
        msg.append(owh.getGraphId());
        msg.append(").");

        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Hunt Save Status", msg.toString()));
    }

GraphRepository save :

    @Transactional
    public OutfitterWaterfowlHunt saveOutfitterWaterfowlHunt(OutfitterWaterfowlHunt owh)
    {
        OutfitterWaterfowlHunt owHunt = this.outWtrFwlRepo.save(owh);

        return owHunt;
    }

Any ideas on what is causing the server to suddenly start failing when saving this node? I currently have 19957 nodes in my Neo4j db of which most make up my DateTree. Is there a better way to save the Node than what I'm currently doing?

TIA


Answer:

This issue has been fixed in the yet-to-be-released neo4j-ogm 2.0. The hard way to fix this in the meantime is to save all related entities first and then the top level entity, but looking at your model, this isn't straightforward.

Question:

I've got a neo4j graph with the following structure.

(Account) ---[Transaction]--- (Account)

Transaction is a neo4j relationship and Account is a node.

There are set various properties on each transaction, such as the transaction ID, amount, date, and various other banking information.

I can run a search by Account id, and it returns fine. However when I search by transaction ID, neo4J searches the entire graph instead of using index, and the search fails.

I created indexes using org.neo4j.unsafe.batchinsert.BatchInserterImpl.createDeferredSchemaIndex() for both Account.number and Transaction.txid. The index seems to function for Account (node) searches but not for Transaction (relationship) searches. (I have also enabled auto index for node and relationship, but it doesnt change things)

I'm thinking that indexing on relationship properties is not supported, so considering making intermediate nodes to hold property information. However, I'd prefer to stick to my original design if possible.

Any idea how to proceed?


Answer:

You can use legacy indexes or auto indexes to index relationships. Schema indexes do not support indexing relationships.

The reason for that: Typically you use use nodes to model the #things# or #entities# in your domain. Relationship wire up your world and put the nodes into a semantic context. When following that model you typically don't have to index relationships since your queries always start at a #thing# which is always a node.

In your model, you should rethink modelling, I guess it could make sense to have

 (account)-[:send]->(transaction)-[:to]->(account).

So transactions are a thing on its own and therefore become nodes.

Question:

I am currently working on a program using graph db - neo4j and I need to realize the following function.

  1. I have two types of nodes, type A means stage, type N means let the user do some selections.
  2. First we have node A1, which has several (2-5) type N children, N1, N2, N3, ...
  3. Nodes A1 also have child nodes A2, A3, ...
  4. In java, after arriving at A1, I will ask the users to do some selections according to Ni, then go to a type A child based on a function of the selections. For example, if N1=true, N2=true, N3=false, I go to A2, otherwise, I goto A3.

BTW, I will meet this situation many many times in my program. Do you guys have any idea how to implement it efficiently.

Thanks in advance.


Answer:

Suggestion for Setup

(Ax)-[:TRUE ]->(Nx)-[:TRUE ]->(Ax+1)
(Ax)-[:FALSE]->(Nx)-[:FALSE]->(Ax+1)

Suggestion for Query

 MATCH (a:A {id:1}),
       (a)-[:TRUE]-> (n)-[:FALSE]->(a2),
       (a)-[:FALSE]->(n2)-[:TRUE]->(a2),
       (a)-[:TRUE]-> (n)-[:FALSE]->(a2)
 RETURN a2;

Question:

I have a query on how to save a Subclass or ArrayList inside the relationship entity?

My issue: When I pass data to the save call from the repository to save Child as part of the Parent there is no issue or error, but when I retrieve or lookup in the database there is no data present.

Parent Class:

@RelationshipEntity(type = "HAS_DATA")
public class Parent{

private Long id;

private Long sequenceId;

Set<Child> = new HashSet<>();

@StartNode
SomeClass1 someClass1;

@EndNode
SomeClass2 someClass2;

//Getter and Setters
}

Child Class:

public class Child{

Long Id;

String name;

//Getters and Setters
}

How do I achieve this?


Answer:

Take a look at the AttributeConverter annotation, however if you need a collection of values on a relationship, consider refactoring your model, to make it a node, with related things.

Example:

Here is an an example attribute converter (in Kotlin) that converts to/from a string array property in Neo4j, to a Java type.

class RoleArrayAttributeConverter : AttributeConverter<Array<Role>, Array<String>>
{

    override fun toEntityAttribute(value: Array<String>): Array<Role>
    {
        return value.map { Role.valueOf(it) }.toTypedArray()
    }

    override fun toGraphProperty(value: Array<Role>): Array<String>
    {
        return value.map { it.toString() }.toTypedArray()
    }

}

Question:

I found out about Neo4j OGM yesterday and quickly made a new project to test out how it works. One problem I've come across is setting Relationhip properties as this is crucial for my project. Here's an example:

Room Node:

@NodeEntity
public class Room {

@GraphId
Long id;

@Property(name="name")
String name;

@Relationship(type="CONNECTS")
List<Room> rooms;

public List<Room> getRooms() {
    if(rooms == null)
        rooms = new ArrayList<Room>();

    return rooms;
}

public void setRooms(List<Room> rooms) {
    this.rooms = rooms;
}

public Room(String name){
    this.name = name;
}

public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}


public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public Room(){
}

public void connectsTo(Room room){
    this.getRooms().add(room);
}
}

Connects Node (Relation):

@RelationshipEntity(type="CONNECTS")
public class Connects {

@GraphId
Long id;

@StartNode
Room startMapNode;

@EndNode
Room endMapNode;

@Property(name="length")
int length;

public Connects(Room startMapNode, Room endMapNode){
    this.startMapNode = startMapNode;
    this.endMapNode = endMapNode;
}

public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}

public Room getStartMapNode() {
    return startMapNode;
}

public void setStartMapNode(Room startMapNode) {
    this.startMapNode = startMapNode;
}

public Room getEndMapNode() {
    return endMapNode;
}

public void setEndMapNode(Room endMapNode) {
    this.endMapNode = endMapNode;
}

public int getLength() {
    return length;
}

public void setLength(int length) {
    this.length = length;
}

public Connects(){

}

}

Main method:

public static void main(String[] args) {
    SessionFactory sessionFactory = new SessionFactory("at.htl.in110010.domain");
    Session session  = sessionFactory.openSession("http://localhost:7474");

    session.purgeDatabase();

    Room roomOne = new Room("TEST_ROOM_ONE");
    Room roomTwo = new Room("TEST_ROOM_TWO");

    roomOne.connectsTo(roomTwo);
    roomTwo.connectsTo(roomOne);

    Connects connectRelation = new Connects(roomOne,roomTwo);
    connectRelation.setLength(2);

    session.save(connectRelation);

}

Now as you can see I've set the length in my main method, but when I check the database under http://localhost:7474 it shows the relation between the nodes but says it has no properties.

Here is the console output: http://pastebin.com/CByfmVcR

Any help in setting the Property would be very appreciated. Or is there perhaps a different/easier way of mapping objects to the neo4J database ?

Thanks


Answer:

Using a relationship entity is the right thing to do as you have properties on the relationship. But this also means that your relationship between rooms is represented by Connects. So, Room should have a reference to Connects rather than to the other Room directly.

e.g.

@Relationship(type="CONNECTS")
List<Connects> rooms;

Here's a test that resembles your domain model:

https://github.com/neo4j/neo4j-ogm/tree/master/src/test/java/org/neo4j/ogm/domain/friendships and

https://github.com/neo4j/neo4j-ogm/blob/master/src/test/java/org/neo4j/ogm/integration/friendships/FriendshipsRelationshipEntityTest.java

I notice you're using neo4j-ogm 1.1.3. Please upgrade to 1.1.4 as it contains important fixes.

Question:

I'm having problems with relation

@RelationshipEntity(type = RelTypes.Tag.TAG_ON_OBJECT_EVALUATION)
public class TagOnObjectEvaluation
{
  @StartNode
  private Mashup taggableObject;

  @EndNode
  private Tag tag;

  // Other fields, getters and setters
}

In both the entities involved (Mashup and Tag), I have this field (with opposite Direction)

@RelatedToVia(type = RelTypes.Tag.TAG_ON_OBJECT_EVALUATION,
      direction = Direction.INCOMING /*Direction.OUTGOING*/)
  private Set<TagOnObjectEvaluation> tagOnObjectEvaluations =
      new HashSet<TagOnObjectEvaluation>();

Then, I have various service class to manage Tag, Mashup and TagOnObjectEvaluation. The class under test now is the latter. Note: the name is a bit confusing and it's a legacy from the previous coder, you can read DAO as Service. Also GenericNeo4jDAOImpl (again, read it as GenericServiceNeo4jImpl) simply defines standard methods for entities management (create(), find(), update(), delete(), fetch() )

@Service
public class TagOnObjectEvaluationDAONeo4jImpl extends
    GenericNeo4jDAOImpl<TagOnObjectEvaluation> implements
    TagOnObjectEvaluationDAO
{
  @Autowired
  private TagOnObjectEvaluationRepository repository;

  public TagOnObjectEvaluationDAONeo4jImpl()
  {
    super(TagOnObjectEvaluation.class);
  }

  public TagOnObjectEvaluationDAONeo4jImpl(
      Class<? extends TagOnObjectEvaluation> entityClass)
  {
    super(entityClass);
  }

  @Override
  public TagOnObjectEvaluation create(TagOnObjectEvaluation t)
  {
    Transaction tx = template.getGraphDatabaseService().beginTx();
    TagOnObjectEvaluation savedT = null;
    try
    {
      // This is to enforce the uniqueness of the relationship. I know it can fail in many ways, but this is not a problem ATM 
      savedT =
          template.getRelationshipBetween(
              t.getTaggableObject(), t.getTag(),
              TagOnObjectEvaluation.class,
              RelTypes.Tag.TAG_ON_OBJECT_EVALUATION);
      if (savedT == null)
        savedT = super.create(t);
      tx.success();
    }
    catch (Exception e)
    {
      tx.failure();
      savedT = null;
    }
    finally
    {
      tx.finish();
    }
    return savedT;
  }
}

It seems pretty straightforward until now. But when I'm trying to persist a RelationshipEntity instance, I have many problems.

  @Test
  public void testRelationshipEntityWasPersisted()
  {
    TagOnObjectEvaluation tagOnObjectEvaluation = new TagOnObjectEvaluation(taggedObject, tag);

    tagOnObjectEvaluationDao.create(tagOnObjectEvaluation);
    assertNotNull(tagOnObjectEvaluation.getId());
    LOGGER.info("TagOnObjectEvaluation id = " + tagOnObjectEvaluation.getId());

    tagDao.fetch(tag);
    assertEquals(1, tag.getTaggedObjectsEvaluations().size());
  }

The last test fail: the size is 0 and not 1. Also, although it seems that the entity is correctly stored (it gets an id assigned), if I'm navigating the db later on there is no track of it at all. I've also tried to add the relationship in a different way, using the sets of the involved nodes; f.e.

tag.getTaggedObjectsEvaluations().add(tagOnObjectEvaluation);
tagDao.update(tag); 

but with no improvements at all.


Answer:

You need to change the direction of the relationship in your entity Mashape, (entity corresponding to the @StartNode of your @RelationshipEntity TagOnObjectEvaluation).

@NodeEntity
class Mashape {

   // ...
   @RelatedToVia(type = RelTypes.Tag.TAG_ON_OBJECT_EVALUATION, direction = Direction.OUTGOING)
   private Set<TagOnObjectEvaluation> tagOnObjectEvaluations = new HashSet<TagOnObjectEvaluation>();    

}

Just point that according to the specifications of @RelatedToVia spring-data-neo4j annotation, the direction by default is OUTGOING, so you really don't need to specify the direction in this case. This also should be correct:

   @RelatedToVia(type = RelTypes.Tag.TAG_ON_OBJECT_EVALUATION)
   private Set<TagOnObjectEvaluation> tagOnObjectEvaluations = new HashSet<TagOnObjectEvaluation>();    

Hope it helps.

Question:

I've coded a traversal in Java which returns an Iterable. The worst case is an Iterable size of 850784 relationships.

Objective: I want to sample (without replacement) only 20 relationships and I want to do it fast.

Solution 1 : performing a toList() or casting it in some sort of Collection takes too much time (> 1 minute). I know I could have taken avantage of the shuffle() function, etc. but it was unacceptable.

Solution 2: thus, in order to do this directly on the Iterable, I've used the guava collect library, I have included the time in milliseconds (calculated with System.nanoTime() and dividing by 1000000), for each of the 3 steps below. I need to take the size of the Iterable for the random number generator, this is a real bottleneck.

    /* TRAVERSAL: 5 ms */
    Iterable<Relationship> simrels = traversal1.traverse(user).relationships();

    /* GET ITERABLE SIZE: 74669 ms */
    int simrelssize = com.google.common.collect.Iterables.size(simrels);

    /* RANDOM SAMPLE OF 20: 28321 ms*/
    long seed = System.nanoTime();
    int[] idxs = new int[20];
    Random randomGenerator = new XSRandom(seed);
    for (int i = 0; i < idxs.length; ++i){
        int randomInt = randomGenerator.nextInt(simrelssize);
        idxs[i]=randomInt;
    }
    Arrays.sort(idxs);

    List<Relationship> simrelslist2 = new ArrayList<Relationship>();
    for(int i = 0; i < idxs.length; ++i){
        if (i > 0) {
            int pos = idxs[i]-idxs[i-1];
            simrelslist2.add(com.google.common.collect.Iterables.get(simrels, pos));
        }
        else{
            simrelslist2.add(com.google.common.collect.Iterables.get(simrels, idxs[i]));
        }
    }

How can I optimize this code to go faster ?

Note: I have a Windows 8.1 PC, i5 2.30GHz, RAM 16GB, HDD 1TB

As per Michal's request, please find file contents below:

neo4j-wrapper

#********************************************************************
# Property file references
#********************************************************************

wrapper.java.additional=-Dorg.neo4j.server.properties=conf/neo4j-server.properties
wrapper.java.additional=-Djava.util.logging.config.file=conf/logging.properties
wrapper.java.additional=-Dlog4j.configuration=file:conf/log4j.properties

#********************************************************************
# JVM Parameters
#********************************************************************

wrapper.java.additional=-XX:+UseConcMarkSweepGC
wrapper.java.additional=-XX:+CMSClassUnloadingEnabled
wrapper.java.additional=-XX:-OmitStackTraceInFastThrow

# Remote JMX monitoring, uncomment and adjust the following lines as needed.
# Also make sure to update the jmx.access and jmx.password files with appropriate permission roles and passwords,
# the shipped configuration contains only a read only role called 'monitor' with password 'Neo4j'.
# For more details, see: http://download.oracle.com/javase/7/docs/technotes/guides/management/agent.html
# On Unix based systems the jmx.password file needs to be owned by the user that will run the server,
# and have permissions set to 0600.
# For details on setting these file permissions on Windows see:
#     http://docs.oracle.com/javase/7/docs/technotes/guides/management/security-windows.html
#wrapper.java.additional=-Dcom.sun.management.jmxremote.port=3637
#wrapper.java.additional=-Dcom.sun.management.jmxremote.authenticate=true
#wrapper.java.additional=-Dcom.sun.management.jmxremote.ssl=false
#wrapper.java.additional=-Dcom.sun.management.jmxremote.password.file=conf/jmx.password
#wrapper.java.additional=-Dcom.sun.management.jmxremote.access.file=conf/jmx.access

# Some systems cannot discover host name automatically, and need this line configured:
#wrapper.java.additional=-Djava.rmi.server.hostname=$THE_NEO4J_SERVER_HOSTNAME

# Uncomment the following lines to enable garbage collection logging
#wrapper.java.additional=-Xloggc:data/log/neo4j-gc.log
#wrapper.java.additional=-XX:+PrintGCDetails
#wrapper.java.additional=-XX:+PrintGCDateStamps
#wrapper.java.additional=-XX:+PrintGCApplicationStoppedTime
#wrapper.java.additional=-XX:+PrintPromotionFailure
#wrapper.java.additional=-XX:+PrintTenuringDistribution

# Java Heap Size: by default the Java heap size is dynamically
# calculated based on available system resources.
# Uncomment these lines to set specific initial and maximum
# heap size in MB.
wrapper.java.initmemory=8192
wrapper.java.maxmemory=10240

#********************************************************************
# Wrapper settings
#********************************************************************
# path is relative to the bin dir
wrapper.pidfile=../data/neo4j-server.pid

#********************************************************************
# Wrapper Windows NT/2000/XP Service Properties
#********************************************************************
# WARNING - Do not modify any of these properties when an application
#  using this configuration file has been installed as a service.
#  Please uninstall the service before modifying this section.  The
#  service can then be reinstalled.

# Name of the service
wrapper.name=neo4j

# User account to be used for linux installs. Will default to current
# user if not set.
wrapper.user=

neo4j.properties

# Enable this to be able to upgrade a store from an older version.
#allow_store_upgrade=true

# The amount of memory to use for mapping the store files, either in bytes or
# as a percentage of available memory. This will be clipped at the amount of
# free memory observed when the database starts, and automatically be rounded
# down to the nearest whole page. For example, if "500MB" is configured, but
# only 450MB of memory is free when the database starts, then the database will
# map at most 450MB. If "50%" is configured, and the system has a capacity of
# 4GB, then at most 2GB of memory will be mapped, unless the database observes
# that less than 2GB of memory is free when it starts.
#mapped_memory_total_size=50%

# Enable this to specify a parser other than the default one.
#cypher_parser_version=2.0

# Keep logical logs, helps debugging but uses more disk space, enabled for
# legacy reasons To limit space needed to store historical logs use values such
# as: "7 days" or "100M size" instead of "true".
#keep_logical_logs=7 days

# Autoindexing

# Enable auto-indexing for nodes, default is false.
#node_auto_indexing=true

# The node property keys to be auto-indexed, if enabled.
#node_keys_indexable=name,age

# Enable auto-indexing for relationships, default is false.
#relationship_auto_indexing=true

# The relationship property keys to be auto-indexed, if enabled.
#relationship_keys_indexable=name,age

# Enable shell server so that remote clients can connect via Neo4j shell.
#remote_shell_enabled=true
# The network interface IP the shell will listen on (use 0.0.0 for all interfaces).
#remote_shell_host=127.0.0.1
# The port the shell will listen on, default is 1337.
#remote_shell_port=1337

# The type of cache to use for nodes and relationships.
#cache_type=hpc

# Maximum size of the heap memory to dedicate to the cached nodes.
#node_cache_size=

# Maximum size of the heap memory to dedicate to the cached relationships.
#relationship_cache_size=

# Enable online backups to be taken from this database.
online_backup_enabled=true

# Port to listen to for incoming backup requests.
online_backup_server=127.0.0.1:6362


# Uncomment and specify these lines for running Neo4j in High Availability mode.
# See the High availability setup tutorial for more details on these settings
# http://neo4j.com/docs/2.2.0-M02/ha-setup-tutorial.html

# ha.server_id is the number of each instance in the HA cluster. It should be
# an integer (e.g. 1), and should be unique for each cluster instance.
#ha.server_id=

# ha.initial_hosts is a comma-separated list (without spaces) of the host:port
# where the ha.cluster_server of all instances will be listening. Typically
# this will be the same for all cluster instances.
#ha.initial_hosts=192.168.0.1:5001,192.168.0.2:5001,192.168.0.3:5001

# IP and port for this instance to listen on, for communicating cluster status
# information iwth other instances (also see ha.initial_hosts). The IP
# must be the configured IP address for one of the local interfaces.
#ha.cluster_server=192.168.0.1:5001

# IP and port for this instance to listen on, for communicating transaction
# data with other instances (also see ha.initial_hosts). The IP
# must be the configured IP address for one of the local interfaces.
#ha.server=192.168.0.1:6001

# The interval at which slaves will pull updates from the master. Comment out
# the option to disable periodic pulling of updates. Unit is seconds.
ha.pull_interval=10

# Amount of slaves the master will try to push a transaction to upon commit
# (default is 1). The master will optimistically continue and not fail the
# transaction even if it fails to reach the push factor. Setting this to 0 will
# increase write performance when writing through master but could potentially
# lead to branched data (or loss of transaction) if the master goes down.
#ha.tx_push_factor=1

# Strategy the master will use when pushing data to slaves (if the push factor
# is greater than 0). There are two options available "fixed" (default) or
# "round_robin". Fixed will start by pushing to slaves ordered by server id
# (highest first) improving performance since the slaves only have to cache up
# one transaction at a time.
#ha.tx_push_strategy=fixed

# Policy for how to handle branched data.
#branched_data_policy=keep_all

# Clustering timeouts
# Default timeout.
#ha.default_timeout=5s

# How often heartbeat messages should be sent. Defaults to ha.default_timeout.
#ha.heartbeat_interval=5s

# Timeout for heartbeats between cluster members. Should be at least twice that of ha.heartbeat_interval.
#heartbeat_timeout=11s

Answer:

The reason Neo4j is returning an Iterable is that it executes the traversal whilst you're iterating. In order to sample, I'm afraid you have to "visit" every relationship. Yes, you can skip some, but you still have to iterate through all of them at the end of the day.

We're using "reservoir sampling" algorithm for this, implemented here. Not sure it's gonna perform much better though, for the reason described above. That said you should be able to sample 1M relationships in less than 1 second with warm cache. If it's taking longer than that, you may need to tweak the memory settings a bit.

Question:

I have created the following query which works fine when i am passing all the relationship.

@Query(value="START profile=node(*) "
                + "MATCH profile - [rel:create | stream | like | follow] - feeds "
                + "WHERE profile.id ={profileId} "
                + "WITH distinct feeds as feed, rel.date as date "
                + "ORDER BY date DESC "
                + "RETURN DISTINCT feed;")
    public List<Feed> findByProfileId(@Param("profileId") String profileId);

but i want to fetch data for specific action like the query below

@Query(value="START profile=node(*) "
                + "MATCH profile - [rel:{action}] - feeds "
                + "WHERE profile.id ={profileId} "
                + "WITH distinct feeds as feed, rel.date as date "
                + "ORDER BY date DESC "
                + "RETURN DISTINCT feed;")
public List<Feed> findByProfileId(@Param("action") String action,@Param("profileId") String profileId);

But this does not work and i get the following error.

org.neo4j.rest.graphdb.RestResult Exception: Invalid input '{': expected whitespace or an identifier

I think this is not a correct way to pass param in relationship. Is there a way how i can achieve this.


Answer:

  1. you should not use start n=node(*) in your query use a label like :Profile and an index/unique constraint on :Profile(id)

    MATCH (profile:Profile) WHERE profile.id = {profileId}

  2. you can't use rel-types as parameters directly but you can check the type of your relationship, e.g. against a set of names that is passed in

either

WHERE type(rel) = {action}

or

WHERE type(rel) IN {actions}

Question:

In older versions SDN we had following interface for repositories

org.springframework.data.neo4j.repository.RelationshipOperationsRepository;

public interface UserRelationRepository extends GraphRepository<UserEntity>, RelationshipOperationsRepository<UserEntity> {

 MakeFriend rel = userRepository.getRelationshipBetween(startUser, endUser, MakeFriend.class, RelTypes.FRIEND.name());
        if (rel != null) {
            startUser.getFirstname() + " + " + endUser.getFirstname());
        }

        userRepository.createRelationshipBetween(startUser, endUser, MakeFriend.class, RelTypes.FRIEND.name());
        userRepository.createRelationshipBetween(endUser, startUser, MakeFriend.class, RelTypes.FRIEND.name());

But current version does not support it. Which is the best way implement functionality like createRelationshipBetween or getRelationshipBetween in SDN?


Answer:

SDN 4 does not support managing low-level graph operations using APIs.

Instead the graph operations to be performed are inferred from your domain model classes and what you do with them.

For example, create a User class as follows:

class User {

   List<User> friends = new ArrayList();
}

If you now adding or remove Users in the friends list and save the User in the normal way via the standard Repository methods, this will achieve what you need automatically - the appropriate relationships will be added/removed. You don't have to tell SDN what to do because the point of an ORM/OGM is to hide you from the underlying data model and its implementation details and allow you to manipulate the domain model itself.

If you really need to perform these low-level operations directly on the graph, you use Cypher with a query method.

You can find out more about SDN 4.1 here

Question:

I want to query for a set of nodes a subset of their attributes, their relationships and the target nodes with some of their attributes (from Java with CYPHER via REST). My idea was the following:

MATCH a WHERE id(a) IN {ids}
OPTIONAL MATCH (a)-[r]->(b)
RETURN id(a), a.name, a.attr1, r.attr2, id(b), b.name

Now I get a "row" for every relationship, but it contains the the data for every node "a" multiple times.

Is there a better way to make such a query so that attribute(s) for the nodes "a" are transferred only once? One idea is to make 2 separate queries, but if the WHERE condition is a little bit more complex, it may be executed twice.


Answer:

Use collect function.

http://neo4j.com/docs/stable/query-aggregation.html#aggregation-collect

MATCH a WHERE id(a) IN {ids}
OPTIONAL MATCH (a)-[r]->(b)
RETURN id(a), a.name, a.attr1, collect([r.attr2, id(b), b.name])

Question:

I have the following nodes:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@NodeEntity
public class Person {

  @Id
  @GeneratedValue
  private Long id;

  private String firstName;

  private String lastName;

  private LocalDate birthday;

  @Email
  private String email;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@NodeEntity
public class Skill {

  @Id
  @GeneratedValue
  private Long id;

  private String name;

  private String description;
}

And this RelationshipEntity:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RelationshipEntity("RATED")
public class SkillRating {

  @Id
  @GeneratedValue
  private Long id;

  @Min(0)
  @Max(100)
  private Integer score;

  private LocalDate measurementDate;

  @StartNode
  private Person person;

  @EndNode
  private Skill skill;
}

I do not want to load relationships which I do not plan to use, i.e. I do not want to add:

@Relationship(type = "RATED")
private Set<SkillRating> skillRatings;

to my Person class definition to prevent these ratings from loading each time I load a Person. I would like to instead load these when necessary using Repository methods. This is what I tried using my knowledge of JPA repositories:

@Repository
public interface SkillRatingRepository extends Neo4jRepository<SkillRating, Long> {
  List<SkillRating> findAllByPerson(Person person);
}

But this method does not work as expected since it does not find any ratings for a person. What am I doing wrong?

-- EDIT --

MATCH (p)-[r:RATED]->(skill) WHERE id(p)={personId} RETURN r

I believe this is a query that solves my problem written in Neo4j Cypher query language. How can I "translate" it in a Repository method using my current class setup?


Answer:

thanks for asking.

As you noticed, we don't support derived finder methods based on objects. With @RelationshipEntity there's the additional restriction, that the derived finder methods only targets properties and not the end or start node.

Having said that, I have taken your project (the domain classes) and created a solution for you. So, domain classes Person, Skill and SkillRating can be used as is.

Please declare your SkillRatingRepository like this:

import java.util.List;

import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface SkillRatingRepository extends Neo4jRepository<SkillRating, Long> {

    @Query("MATCH (p)-[r:RATED]->(skill) WHERE id(p) = :#{#person.id} RETURN p, r, skill")
    List<SkillRating> findAllByPerson(Person person);
}

@Query indicates a custom query. Inside that custom query, you can use Spring Exression Language (SpEL) as described here https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions.

Thus, you're dereference the passed person and access the id. Pretty much as in the query you already wrote. Take note that you have to return start and end node as well to make the mapping work.

If you run a standard Spring Boot project, than parameter names are retained during compilation and no other annotation is needed. If you don't retain them, please add @Param("person") to the parameter.

I noticed that you're using a LocalDate in your domain. Those are supported natively with Neo4j 3.4+ and the Java (aka Bolt) Driver.

In the current version of Spring Data Neo4j and Neo4j-OGM distributed with Spring Boot 2.1.8 those can be activated as shown in the following test (scroll down to the Config class annotated with @TestConfiguration):

import static org.assertj.core.api.Assertions.*;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.ogm.config.Configuration;
import org.neo4j.ogm.driver.ParameterConversionMode;
import org.neo4j.ogm.session.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

@SpringBootTest
@RunWith(SpringRunner.class)
@TestConfiguration
public class SkillRatingRepositoryTest {

    @Autowired
    private SkillRatingRepository skillRatingRepository;

    @Autowired
    private Session session;

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Test
    public void retrievalOfSkillsShouldWork() {
        Skill s = Skill.builder().name("Java")
            .description("The Number one programming language everyone loves and hates").build();

        new TransactionTemplate(transactionManager).execute(t -> {
            session.purgeDatabase();
            return null;
        });

        Person ms = Person.builder()
            .firstName("Michael")
            .lastName("Simons")
            .build();
        Person gm = Person.builder()
            .firstName("Gerrit")
            .lastName("M")
            .build();
        SkillRating r1 = SkillRating
            .builder().person(ms)
            .skill(s).measurementDate(LocalDate.now()).score(23).build();
        SkillRating r2 = SkillRating
            .builder().person(gm)
            .skill(s).measurementDate(LocalDate.now()).score(42).build();

        skillRatingRepository.saveAll(Arrays.asList(r1, r2));

        List<SkillRating> skillRatings =
            skillRatingRepository.findAllByPerson(gm);
        assertThat(skillRatings).hasSize(1);
    }

    @TestConfiguration
    static class Config {

        @Bean
        public org.neo4j.ogm.config.Configuration configuration() {
            Configuration.Builder builder = new org.neo4j.ogm.config.Configuration.Builder();
            builder.uri("bolt://localhost:7687");
            builder.credentials("neo4j", "secret");
            builder.withCustomProperty(ParameterConversionMode.CONFIG_PARAMETER_CONVERSION_MODE,
                ParameterConversionMode.CONVERT_NON_NATIVE_ONLY);
            return builder.build();
        }
    }
}

Notice that I neither use an embedded instance for testing nor the @DataNeo4jTest. Wanted to review the created data inside my locally running instance.

I also recommend not using the embedded database in test but test containers aka "the real thing" you would run in production: https://medium.com/neo4j/testing-your-neo4j-based-java-application-34bef487cc3c

For final reference, here is the POM i used. Please accept the answer if the thing is useful and solves your issue.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>neo4j</groupId>
    <artifactId>so_re</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>so_re</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Question:

In SDN4 I wish to persist a @RelationshipEntity which is not a @NodeEntity's property. Example:

@NodeEntity
public class User{
    Long id;
}

@RelationshipEntity(type="FOLLOWS")
public class Follows{
    @GraphId   private Long relationshipId;
    @StartNode private User follower;
    @EndNode   private User followee;
    @Property  private Date from;

    public Follows(){}
    public Follows(User u1, User u2){
         this.follower = u1;
         this.followee = u2;
    }
}

@Repository
interface FollowsRepository extends GraphRepository<Follows>{}

And then persist the Follows @Relationship like this

...
followsRepository.save(new Follows(user1, user2));
...   

But when doing so, the Relationship is not persisted!!

Sadly as stated in the accepted answer this cannot (yet) be done (SDN 4.0.0.RELEASE)

Workaround 1

It is possible to persist @RelationshipEntities using @Query in GraphRepositories.

@Query("Match (a:User), (b:User) WHERE id(a) = {0} 
                     AND id(b) = {1} CREATE (a)-[r:FOLLOWS {date:{2}}]->(b) RETURN r ")
Workaround 2

This can also be done by treating Follows as a @NodeEntity, which might not be the most performant thing to do BUT will not affect any of the @NodeEntities of the domain, nor the service layer AND you won't have to mess with the depth factor when loading and persisting entities

@NodeEntity
public class User{
    Long id;
}

@NodeEntity
public class Follows{
    private Long Id;
    @Relationship(type="FOLLOWER")
    private User follower;
    @Relationship(type="FOLLOWEE")
    private User followee;
    private Date from;

    public Follows(){}
    public Follows(User u1, User u2){
         this.follower = u1;
         this.followee = u2;
    }
}

....
//Placed in userService
Follows createFollowsRelationship(User u1, User u2){
   return followsRepository.save(new Follows(user1, user2));
}
.....

Answer:

At the moment, you cannot persist a relationship entity directly when it is not referenced from participating node entities. You'll have to save the start node and make sure it has a reference to the relationship entity.

There will be some enhancements around how relationship entities are persisted but not in the next release.

Question:

It´s possible to use a dynamic value for Relation Nodes? I would like to set different Relation make for each graph on Neo4j, I think this could increase the performance of Neo4j, but I would like to know if it´s possible to use OGM on Java with dynamic value for the relations.

Thanks a lot.


Answer:

To create relationships with dynamic types you can install APOC Procedures and use the procedure apoc.create.relationship. This procedure creates relationships with dynamic relationship type.

For example:

with "REL_TYPE" as reltype
match (n1:Node {id:1}), (n2:Node {id:2})
call apoc.create.relationship(n1, reltype,{}, n2) yield rel
return *

will create a relationship -[:REL_TYPE]- between n1 and n2.

With this approach you can pass the relationship type string as parameter to Neo4j in your Java application, then call apoc.create.relationship.

Question:

I am new to Neo4j, and am going through Spring-data-Neo4j documentation. Currently I am not clear about defining the node relationships within the entity beans.

We can have @Relationship on a property, with direction as INCOMING or OUTGOING. Also we can have @RelationshipEntity to define @StartNode and @EndNode. @RelationshipEntity is required if we have additional properties on the relationship. But once we define a Relationship entity do we still need @Relationship on either nodes in the relation? Do we need to define all relationships in Entity class on both side? How to decide? Do defining all relationships on both sides affect the performance?


Answer:

When using relationship entities, the current release of SDN 4 requires you to reference it from at least the start node entity.

You should also reference it from the end node entity if you plan to persist the end node entity and expect the relationship entity to be persisted as well.

The recommendation is to have your object model represented as closely as possible to your graph model. Examples and more explanation at http://graphaware.com/neo4j/2015/09/03/sdn-4-object-model.html

Note that if you have no properties on the relationship, then you must not use a RelationshipEntity and instead use regular @Relationships

Question:

I'm building a simple app using Neo4J and I'm using the Java API.

I have two node labels in my graph 1. Class 2. Instance

I want to get the shortest path between two nodes, (a:Instance) and (b:Instance)

Assume that there are 2 paths connecting the nodes.

Path 1:

(a:Instance)-[:is_a]->(x:Class)<-[:is_a]-(b:Instance)

Path 2:

(a:Instance)-[:relType1]->(z:Instance)-[:relType2]->(y:Instance)<-[relType3]-(b:Instance)

What I want for the result is the Path 2. I want the path is only involving nodes with Instance label. Or if I can't do that, I want the path that doesn't contain an is_a relationship type.

My current code is something like this:

PathFinder<Path> finder = GraphAlgoFactory.shortestPath(PathExpanders.allTypesAndDirections(), CommonCons.MAX_HOP );
Node startNode;
Node endNode;

Path path = finder.findSinglePath(startNode, endNode);

In PathExpanders, I don't see there is exist a filter for specific node label. And also I don't see in PathExpander that can includes all relationship types except one particular relationship type.

Is there a way to achieve that?? I just don't want a node with a Class label or a relationship with is_a type exist in my path.

Sorry for my English

Thank you


Answer:

I solved it

I used PathExpanderBuilder to build my own PathExpander. Firstly, I set my PathExpander to cover all types and directions. After that, I removed the is_a RelationshipType from the expander.

My final code is look like this:

PathExpanderBuilder peBuilder = PathExpanderBuilder.allTypesAndDirections().remove(RelationshipType.withName(CommonCons.REL_IS_A));
PathFinder<Path> finder = GraphAlgoFactory.shortestPath(peBuilder.build(), CommonCons.MAX_HOP );
Node startNode;
Node endNode;

Path path = finder.findSinglePath(startNode, endNode);

Question:

I'm using neo4j's findAll() APIs (OGM 2.0.5) to fetch nodes using filters, paging, sorting and custom depth. Everything is working perfectly, except that generated cypher queries don't take relationships direction into account:

MATCH (n)...
WHERE...
WITH n
MATCH p=(n)-[*0..d]-()
RETURN p

instead of: ...MATCH p=(n)-[*0..d]->()...

Is it really important for me, because not taking relationships directions into account causes huge amount of nodes to be returned by the query. For now my only option is to write manually all my queries in CYPHER.

Some times my queries freeze or are extremely slow. But they are very fast if I specify the direction manually.

It is possible/planned to let configure this behavior in order to have optimized queries based on graph modelisation ?


Answer:

We have a feature request open for this one, you can track it here https://github.com/neo4j/neo4j-ogm/issues/70

Question:

Good Evening,

I am using SDN 3, and am running into problems with removing simple relationships (RelateTo) in my underlying graph. The scenario is that I want to establish a Friend request/approval system amongst Users in my web application. I have no problem issuing the request by creating a "HAS_REQUESTED" relationship between Users. but once the User receiving the friend request hits "approve", the "FRIENDS_WITH" relationship is established without properly removing the "HAS_REQUESTED" relationship. the code below walks through the process:

The relevant Controller method

@RequestMapping(value="/approve/friend/{friendId}")
public String approveFriend(@PathVariable("friendId") String friendId){
    User me = userService.findByEmail(userService.getAuthenticatedUser().getName());
    userService.removeOldRequests(friendId, me);
    userService.approveFriendship(friendId, me);
    return "redirect:/friends";
}

The UserService method in question. 'me' is the authenticated user who originally sent the friend request to 'friendId/friend':

public void removeOldRequests(String friendId, User me){
try{
    User friend = userRepository.findByUserId(friendId);
    friend.addStartNodeForUsersRequestingMe(me, false);
    template.save(friend);
}catch(Exception e){
    e.printStackTrace();
}

and here is my User entity Node (excluding unrelated fields/getters/setters.)

@NodeEntity

public class User {

@GraphId Long nodeId;
@Indexed
String userId;
String username;
String firstName;
String lastName;
String email;
String aboutMe;
String Quote;
String favoriteBook;
int age;
Date userCreation;
String sex;
String password;
Role role;
byte[] picture;


@RelatedTo(type="FRIENDS_WITH", direction=Direction.BOTH)
@Fetch
Set<User> friends;


@RelatedTo(type="HAS_FRIEND_REQUEST")
@Fetch
Set<User> startNodeForUsersRequestingMe;

@RelatedTo(type="HAS_FRIEND_REQUEST", direction=Direction.INCOMING)
@Fetch
Set<User> UsersWhoHaveRequestedMe;


public void addStartNodeForUsersRequestingMe(User user, boolean flag){
    if(flag){
        this.startNodeForUsersRequestingMe.add(user);
    }else{
        this.startNodeForUsersRequestingMe.remove(user);
    }

}
public void addUsersWhoHaveRequestedMe(User user, boolean flag){
    if(flag){
        this.UsersWhoHaveRequestedMe.add(user);
    }else{
        this.UsersWhoHaveRequestedMe.remove(user);
    }

}

The repository method I am using to return the current user's friend requests is below. Right now it is configured to just return any relationship the user has that is "HAS_FRIEND_REQUEST" just for testing purposes to see if I can get User A with one friend request from User B to NOT be returned.

@Query("START user=node({0})"
    +"MATCH (user)-[:HAS_FRIEND_REQUEST]-(friend)"
    + "return friend;")

Iterable getUserFriendRequests(User user);

Any guidance on how to properly remove the "HAS_FRIEND_REQUEST" in a clean manner would be greatly appreciated. either that, or maybe a better way to handle the "friend request Handshake" idea. If you have any questions or concerns for me, please do not hesitate to bring them to my attention.


Answer:

You can remove the target user from the collection or use the Neo4jTemplate method to delete the relationship.

template.deleteRelationshipBetween(Object start, Object end, String type)

Question:

I am using Java SpringBoot and Neo4j as the DB.

I would like to declare a RelationshipEntity with a generic StartNode. Then when I create the object I will pass the NodeEntity that I want the relation to tie to.

How can I do the Relationship class so I don't duplicate for each start/end node type.

Example:

@RelationshipEntity(type = "RESIDES_AT")
public class ResidesAt {

    @StartNode
    private Object startNode; //Can be Company or Person
        ...
    @EndNode
    private Address address;
}

Then in the Person and Company Node class I have:

 @NodeEntity (label="Company")
    public class Company {
        @Relationship(type="RESIDES_AT", direction=Relationship.OUTGOING)
        Set<ResidesAt> residesAt = new HashSet<>();
...
    }

And in the execution I would do something like:

Company createCompany = new Company("Top Mechanic");
Person createPerson = new Person("John", "Doe");
Address createAddress = new Address("John's Home", "123 Mystery Lane", null, "Big City", "UT", "84123", null, "Occupied");
createPerson.residesAt(createAddress, "Home Owner");
createCompany.residesAt(createAddress, "John's Business Mailing Address");

companyRepository.save(createCompany); 
personRepository.save(createPerson);

However; when I try to start the SpringBoot application, I get the following error:

2017-09-29 16:26:26.832  WARN 7564 --- [           main] org.neo4j.ogm.metadata.ClassInfo         : Failed to find an @StartNode on trn.justin.model.relationships.ResidesAt
2017-09-29 16:26:26.832  WARN 7564 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'companyService': Unsatisfied dependency expressed through field 'companyRepository'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'companyRepository': Unsatisfied dependency expressed through method 'setSession' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.data.neo4j.transaction.SharedSessionCreator#0': Cannot resolve reference to bean 'sessionFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.neo4j.ogm.session.SessionFactory]: Factory method 'sessionFactory' threw exception; nested exception is java.lang.NullPointerException
2017-09-29 16:26:26.832  INFO 7564 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2017-09-29 16:26:26.848  WARN 7564 --- [           main] o.s.b.c.e.EventPublishingRunListener     : Error calling ApplicationEventListener

java.lang.ClassCastException: org.springframework.boot.context.event.ApplicationFailedEvent cannot be cast to org.springframework.boot.web.context.WebServerInitializedEvent

Answer:

The best solution I can find is to restructure things so that the "NodeEntity" uses a "RelationshipEntity" class, and the RelationshipEntity class uses a "RelationshipType" class, and have the RelationshipType class holds the common attributes. Example:

@NodeEntity (label="Company")
public class Company {
    ...

    @Relationship(type="RESIDES_AT", direction=Relationship.OUTGOING)
    Set<CompanyResidesAtAddress> residesAt = new HashSet<>();
}

@NodeEntity (label="Address")
public class Address {
   ...
   @Relationship(type="RESIDES_AT", direction=Relationship.INCOMING)
   Set<PersonResidesAtAddress> personResidances = new HashSet<>();

   @Relationship(type="RESIDES_AT", direction=Relationship.INCOMING)
   Set<CompanyResidesAtAddress> companyResidances = new HashSet<>();
}

@RelationshipEntity(type = "RESIDES_AT")
public class CompanyResidesAtAddress {
   ...
   @StartNode
   private Company startNode;

   private ResidesAt residesAt;

   @EndNode
   private Address address;
}

public class ResidesAt implements RelationshipType{

   ... // Common Attributes & methods

   @Override
   public String name() {
      return this.getClass().getSimpleName().toUpperCase();
   }
}

Then in execution I do something like this:

    Company createCompany = new Company("Top Mechanic");
    Person createPerson = new Person("John", "Doe");
    Address createAddress = new Address("John's Home", "123 Mystery Lane", null, "Salt Lake City", "UT", "84120", null, "Occupied");
    createCompany.residesAt(createAddress, "John's Business Mailing Address");
    createPerson.residesAt(createAddress, "Home Owner");

    companyRepository.save(createCompany); 
    personRepository.save(createPerson);

This is working for me, and seems to be the best way I can find.

Question:

I have two NodeEntity classes, which are to be related in a many-to-one relationship (as in, MANY to one). Let's say many entity A's are related to a single entity B. I want to be able to load entity B with a depth greater than 0, but without loading the many related entity A's, and in fact do not ever need to access an entity A from an entity B.

Is it possible to specify the relationship only on entity A, excluding it from entity B, such that loading an entity B would not load any entity A's, but loading an entity A will load an entity B? I'm concerned about saving the entities afterwards, since I don't want to lose the relationship when saving an entity B.


Answer:

Yes, you can do this. Check out this test case

@Test
@Transactional
public void shouldNotDeleteUnmappedRelations() throws Exception {

    session.purgeDatabase();
    session.query("CREATE (a1:A) CREATE (a2:A) CREATE (b:B{name:'b'}) CREATE (a1)-[:REL]->(b) CREATE (a2)-[:REL]->(b) RETURN id(b) as id", Collections.emptyMap());

    Collection<B> res = session.loadAll(B.class, new Filters("name", "b"), 0);
    B b = res.iterator().next();
    assertThat(b).isNotNull();

    session.save(b);
    session.clear();

    Collection<A> allA = session.loadAll(A.class);
    assertThat(allA).hasSize(2);
    assertThat(allA).extracting("b").isNotNull();
}

Question:

I need to find out all the related nodes from a given node. I also need to identify the direction, whether it is incoming or outgoing, plus the ID, labels on each related node. Following is the query I am trying out. Would it be effective query? Is there any other simpler way?

MATCH (o)<-[or]-(e)<-[ir]-(i) 
WHERE e.firstName='Sid' 
RETURN o,ID(o),TYPE(or),or,e,ID(e),TYPE(ir),ir,i,ID(i)

With above query I am able to identify o as outgoing node and i as incoming node.


Answer:

  1. Use Labels + Indexes to find your node
  2. You already specify the direction in your pattern so you know between your nodes
  3. in case you don't you can get the direction regarding a node with:

this statement:

MATCH (n:Foo)-[r]-(m) WHERE n.id = "bar"
RETURN n,m,type(r), (startNode(r) = n) as out_n

Question:

I am trying to run a relationship creation but for some reason I cant use coalesce inside of my relationship query. Im completely stuck and some of this may be malformed but im trying to fix it one step at a time. here is my code

/* Get the kill assassin and student by relation UUID*/
'MATCH (a:Student)-[r:TARGET]->(t:Student) WHERE r.uuid = $uuid ' +
/* Lets Kill the student and set confirmed to true */
'SET r.confirmed = true, t.IsDead = true WITH ' +
/* Delete any teachers that may have the target of the student*/
'MATCH (t)<-[tr:TARGET]-(:Teacher) DELETE r ' +
/* Get the assassinated persons target. Set them as X. */
'MATCH (t)-[ar:TARGET]->(x:Student) WHERE ar.confirmed = false AND x.IsDead = false ' +
/* Lets deal with anyone who inherited our target if we are dead :(*/
'OPTIONAL MATCH (t)<-[sr:TARGET]-(ss:Student) WHERE sr.confirmed = false AND ss.IsDead = false ' +
/* Lets steal their kill node and set them as our new target unless of course someone decided to kill us and nab them */
'CREATE (COALESCE(t,a))-[nr:TARGET {killed:false,confirmed:false}]->(t) '

EDIT: I cleaned up my code A LOT. Just for the record coalesce cant be used in a relationship. Here is the cleaned up code for those wondering.

/**
     * a: The student who made the kill
     * v: The victim of the kill
     * r: the relationship between a and t
     * x: the victims target
     * tr (OPTIONAL) : the teacher who we hired to kill our target
     * ss: The person who will be inheriting t(victim)'s target
     */
    /* Get the Killer, set them as A AND set the Victim as T */
    'MATCH (a:Student)-[r:TARGET {uuid:$uuid,confirmed:false}]->(v:Student),' +
    /* Fetch the Victims Target and set them as x */
    '(v)-[:TARGET {confirmed:false}]->(x:Student {IsDead:false}) ' +
    /* Check if we hired a teacher to assassinate our target. Set the relationship to TR (teacher relation)*/
    'OPTIONAL MATCH (v)<-[tr:TARGET]-(:Teacher) ' +
    /* Fetch the alive person who has the target (in-case we died and then it went into review) */
    'MATCH (v)<-[sr:TARGET {confirmed:false}]-(ss:Student {IsDead:false}) ' +
    /* Create a relationship between SS and t */
    'CREATE (ss)-[:TARGET {killed:false,confirmed:false}]->(x) ' +
    /* Set the Kill Relationship to complete and kill the victim */
    'SET r.confirmed = true, v.IsDead = true,a.Balance = a.Balance + 3 ' +
    /* Delete the teacher relationship */
    'DELETE tr ', {uuid:uuid}

Answer:

You can't use an expression inside that CREATE - but you can move that expression into a WITH statement to assign it to a variable, and then use that variable in your create.

The last line could be replaced by:

/* Lets steal their kill node and set them as our new target unless of course someone decided to kill us and nab them */
WITH COALESCE(t, a) as n, t
CREATE (n)-[nr:TARGET {killed:false,confirmed:false}]->(t)

Which compiles fine, but there are some other problems in the query I had to fix up first:

  • Line 2, SET r.comfirmed... ends in a WITH but there's no variables after it
  • Line 3, MATCH (t)<-... ends in a DELETE but the next line starts with a MATCH - you need a WITH in between the two

So the query I end up with is:

MATCH (a:Student)-[r:TARGET]->(t:Student) WHERE r.uuid = $uuid
/* Lets Kill the student and set confirmed to true */
SET r.confirmed = true, t.IsDead = true 
/* Delete any teachers that may have the target of the student*/
WITH a, t, r
MATCH (t)<-[tr:TARGET]-(:Teacher) DELETE r
/* Get the assassinated persons target. Set them as X. */
WITH a, t
MATCH (t)-[ar:TARGET]->(x:Student) WHERE ar.confirmed = false AND x.IsDead = false
/* Lets deal with anyone who inherited our target if we are dead :(*/
OPTIONAL MATCH (t)<-[sr:TARGET]-(ss:Student) WHERE sr.confirmed = false AND ss.IsDead = false
/* Lets steal their kill node and set them as our new target unless of course someone decided to kill us and nab them */
WITH COALESCE(t, a) as n, t
CREATE (n)-[nr:TARGET {killed:false,confirmed:false}]->(t)

But even then it looks wrong - the COALESCE will always resolve to t because to have gotten that far in the query t must have a value, so the last CREATE will just be creating a relationship between t and itself. Also: the OPTIONAL MATCH introduces the new variable ss but then the variable isn't used anywhere else (making the optional match somewhat redundant).

I'm guessing the COALESCE should be between ss and a, rather than t and a but it's hard to tell.

Question:

I'm designing a Beacon Network, Where beacons are adjacent to each other, using Neo4j database where it has One Entity and One Relationship class. I need to retrieve a Relationship between two Beacons but cant figure out how. Here are the two classes

The Beacon Class

public class Beacon {
    @Id
    private String MAC;
    private String description;
    private String location;

    @Relationship(type = "ADJACENT")
    private List<Adjacent> adjacentList = new ArrayList<>();

    public Beacon() {
    }

    public Beacon(String MAC, String description, String location) {
        this.MAC = MAC;
        this.description = description;
        this.location = location;
    }


    public void addAdjacency(Adjacent adjacent){
        if (this.adjacentList==null){
            this.adjacentList=new ArrayList<>();
        }
        this.adjacentList.add(adjacent);
    }

//Getters and Setters are excluded

}

The Adjacent Relationship Class

public class Adjacent {
    @Id
    @GeneratedValue
    private Long id;

    private int angle;
    private int cost;

    @StartNode
    private Beacon startBeacon;

    @EndNode
    private Beacon endBeacon;

    public Adjacent() {
    }

    public Adjacent(int angle, int cost, Beacon startBeacon, Beacon endBeacon) {
        this.angle = angle;
        this.cost = cost;
        this.startBeacon = startBeacon;
        this.endBeacon = endBeacon;
    }
//Getters and Setters are excluded

}

I already tried creating a Repository and retrieving, but even though the query works in Neo4j Browser, it does not retrieve any data here, just blank parenthesis.

public interface AdjacentRepository extends Neo4jRepository<Adjacent,Long> 
{
@Query("match (b:Beacon{MAC:\"f:f:f:f\"})-[a:ADJACENT]-(c:Beacon{MAC:\"r:r:r:r\") return a")
    Adjacent findaRelationshipp();
}

Any help is greatly appreciated.


Answer:

You'll need to return *, or return a, b, c so the OGM can infer all the details necessary to map the query response to your object model.

The reason the query worked in the Neo4j Browser is because it automatically modifies your query to expand the adjacent path, in this case, the Beacon objects.

Question:

Depending on how I retrieve my node(s) the getRelationships method will either return all relationships (expected) or no relationships (bad).

Neo4j version 2.2.6. Using Java API.

:schema

Indexes
  ON :Lot(lot_id) ONLINE  
  ON :Lot(system) ONLINE  

No constraints

lot_id is always unique. system has only about 3 unique values, not all lots have a system property.

Methods that do return relationships:

  • ResourceIterator<Node> r = graphDb.findNodes(LabelTypes.Lot, "lot_id", lot);

  • Map<String,Object> parms = new HashMap<String,Object>(); parms.put("lots", lots); Result r = graphDb.execute("MATCH (n:Lot) WHERE n.lot_id in {lots} return n;", parms);

Methods that do NOT return relationships:

  • ResourceIterator<Node> r = graphDb.findNodes(LabelTypes.Lot, "system", system);
  • Map<String,Object> parms = new HashMap<String,Object>(); parms.put("lotSystem", system); Result r = graphDb.execute("MATCH (n:Lot) WHERE n.system = {lotSystem} return n;", parms);

The pattern seems to be if I query on lot_id I get relationships, if I query on system I don't get any. No idea why though.

Some additional information from Neo4j GUI:

If I run this query: match (n:Lot) where n.system="SAMPLE" return n limit 1; then I get my single Lot, but if I double click on it in the GUI nothing happens, no relationships are shown.

If I copy the lot_id for this node and run a query just for that node then relationships come back when I double click the node: match (n:Lot) where n.lot_id="someLotId" return n limit 1;


Answer:

I guess that you have problems in your dataset.

Be sure that nodes returned by lot_id and system are really same nodes. Probably you have broken dataset due to import issues.

Todo list: - Use constraints on your dataset - Use merge, to ensure that there is single node in database

Question:

When adding multiple relationships between nodes simultaneously, only some of them are created. In the example below, the calls to makeUser(...) are only populating some of the relationships.

Main

@Transactional
void clearDatabase() {
    session.execute("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r");
}

void createPeople() {

    Person mark = new Person("Mark");
    mark.password = "mark123";
    people.save(mark);

    Organisation noxRentals = new Organisation("Nox Rentals");
    organisations.save(noxRentals);

    makeUser(noxRentals, mark, "Administrator", Right.ADMINISTRATE);
    makeUser(noxRentals, richard, "Administrator", Right.ADMINISTRATE);
    makeUser(classicVillas, mark, "Administrator", Right.ADMINISTRATE);
    makeUser(classicVillas, richard, "Administrator", Right.ADMINISTRATE);
    makeUser(classicVillas, charlotte, "Reservations", Right.LOGIN, Right.SEND_QUOTES);

}

@Transactional
void makeUser (Organisation organisation, Person person, String role, Right...rights) {
    IsUser account = organisation.addUser(person, role);
    account.addRights(rights);
    organisations.save(organisation);
}

void run() {
    clearDatabase();
    createPeople();
}

Resulting in (notice Nox has no relationships):

Organisation.java

@NodeEntity
public class Organisation extends NitroBaseEntity {

    @Relationship(type = "IsUser", direction = Relationship.INCOMING)
    Set<IsUser> users = new HashSet<>();

    public IsUser addUser(Person person, String role) {
        IsUser user = new IsUser(person, this, role);
        this.users.add(user);
        return user;
    }
}

Person.java

@NodeEntity
public class Person extends NitroBaseEntity {

    @Property
    String password;

    @Relationship(type = "IsUser", direction = Relationship.OUTGOING)
    Set<IsUser> users = new HashSet<>();

    public Set<IsUser> getUserAccounts() {
        return this.users;
    }

}

IsUser.java

@RelationshipEntity
public class IsUser {

    @GraphId
    Long id;

    @StartNode
    public Person person;

    @EndNode
    public Organisation organisation;

    @Property
    public String role;

    @Property
    public Set<Right> rights = new HashSet<>();

    public IsUser (Person person, Organisation organisation, String role) {
        this.person = person;
        this.organisation = organisation;
        this.role = role;
    }
}

Complete source code: https://bitbucket.org/sparkyspider/neo4j-sandbox-4/src/22eb3aba82e33dfe473ee15e26f9b4701c62fd8e/src/main/java/com/noxgroup/nitro/config/DatabaseInitializer.java?at=master


Answer:

There are two things missing-

  1. The type has to be specified on the @RelationshipEntity as well, like this @RelationshipEntity(type = "IsUser")
  2. In Organisation.addUser(), add the IsUser to the Person too, something like person.users.add(user);. The entities have to be navigable from both ends.

Question:

I am looking at http://spring.io/guides/gs/accessing-data-neo4j/ which runs fine when using an embedded database.

When running against a neo4j server the entities are saved but when trying to add the relationship and saving I'm getting an exception:

java.lang.AbstractMethodError: org.springframework.data.neo4j.rest.SpringRestGraphDatabase.getOrCreateRelationship(Lorg/neo4j/graphdb/Node;Lorg/neo4j/graphdb/Node;Lorg/neo4j/graphdb/RelationshipType;Lorg/neo4j/graphdb/Direction;Ljava/util/Map;)Lorg/neo4j/graphdb/Relationship;

I have no idea why this is being thrown. In addition it appears that this exception is caught and therefore I am unable to roll back the transaction so I can see the entities in the database after the program has run.

The line which causes the error:

personRepository.save(greg); // <-- This saves fine
greg.worksWith(roy); // Add relationship
greg.worksWith(craig); // Add relationship
personRepository.save(greg); // <-- This causes Spring exception

Answer:

SDN 3 has never been built with remote databases in mind.

Full support for remote databases is delivered by SDN4, the first milestone is available. Read through http://neo4j.com/blog/graphconnect-europe-spring-data-neo4j/ and the linked pages there.

Question:

I want to create unique relationship in neo4j. My requirement is , i have a common resource for all nodes, so if node A is using it, then node B cannot use it, means cannot create relationship of B with resource. So how can i do this ?


Answer:

I'm pretty sure at the moment you can't have neo4j enforce for you. I think you would need to check whenever you try to add the relationship. Here's an example in cypher, though if you're using java you might be using a lower level API:

MATCH (a:LabelA {id: '123abc'), (res:Resource {id: '321cba'})
  WHERE NOT(()-[:has_resource]->res)
  CREATE a-[:has_resource]->res

Question:

I don't have any idea that how to proceed? I am using Cypher Manual for reference.

Question: Each CricketTeam node has a relation type HAS_CONTRACT_WITH with Player nodes. While creating Nodes, how to set a constraint to restrict CricketTeam node to have maximum 17 number of Player nodes connected on the basis of HAS_CONTRACT_WITH relation type?

I am using JAVA and neo4J. I have APOC installed for my database.


Answer:

The short answer is that the current schema constraints do not support limiting the number of certain relationship types on a node. While this may change in the future, it is not currently a supported feature.

You can enforce this yourself through the use of triggers, either provided by your own kernel extensions or via APOC Procedures. You can have these check the degree of the relationship type/direction in question and abort a transaction if this violates your limit.

Question:

Hi I have the the following relationship I would like to express in neo4j and I would like to find the optimal way, or maybe not the optimal but the different ways I can do that in a good way. The case is as follows:

  • I have a Animal
  • Animal can be placed in a Cage or in Open Field.
  • The relationship between the Animal and the Cage is valid within a time period.

The time period is a very important quality so in a way it is a first class citizen of the model. What I am wondering is, if I express this as Neo4J model there are several ways I can do this:

Aproach 1.

  • Animal is a node.
  • Cage/Open Space is a node.
  • Time period becomes a property under the "ASSIGNED" relationship.

Aproach 2.

  • Animal is a node.
  • Time period is a node.
  • Cage/Open Space is a node.

Then the relationship will look like. *Animal --FOR--> Period --Assigned-->CAGE

What questions the model should be capable of answering.

We should be able to collect information if Animals have every been placed together at the same time interval at the same cage. The thing is that One open space may contain many Cages so we would like to find out if two animals have ever been co-located within the same Open Space.


Answer:

Approach 2 is not practical if you have a unique (and therefore shared) Period node for each specific time period, since there could be any number of ASSIGNED relationships for that node, and you would not be able to tell which of those relationships belonged to which animal (without redundantly storing an animal ID in each relationship).

With Approach 1, let's assume this data model (where A represents: "an animal is assigned to a cage that is located in a space", and B represents: "an animal is assigned directly to a space -- not a cage"):

A. (:Animal)-[:ASSIGNED {start: 123, end: 789}]->(:Cage)-[:LOCATED_IN]->(:Space)
B. (:Animal)-[:ASSIGNED {start: 234, end: 777}]->(:Space)

To get info on all animals occupying the same cage at the same time:

MATCH
   (a1:Animal)-[r1:ASSIGNED]->(c:Cage),
   (a2:Animal)-[r2:ASSIGNED]->(c)
WHERE ID(a1) < ID(a2) AND r1.start < r2.end AND r2.start < r1.end
RETURN c, a1, r1, a2, r2

To get info on all animals occupying the same space at the same time:

MATCH
  (a1:Animal)-[r1:ASSIGNED]->()-[:LOCATED_IN*0..1]->(s:Space),
  (a2:Animal)-[r2:ASSIGNED]->()-[:LOCATED_IN*0..1]->(s)
WHERE ID(a1) < ID(a2) AND r1.start < r2.end AND r2.start < r1.end
RETURN s, a1, r1, a2, r2

NOTES:

  • The ID(a1) < ID(a2) test is for avoiding duplicate results (animals 1 and 2, and then again as 2 and 1).
  • The r1.start < r2.end AND r2.start < r1.end test is for detecting overlapping occupancy times.
  • The (x)-[:LOCATED_IN*0..1]->(y) syntax matches a variable-length path of either length 0 (in which case there is no LOCATED_IN relationship, and x is the same as y) or length 1.

Question:

I know that Neo4j does not support explicit encryption and that data can be encrypted by the application before persisting the data into the graph database. But this raises an issue: Suppose the data in the nodes are encrypted, but the relationships between these nodes are still maintained in plaintext. This results in a graph of nodes containing encrypted information, but strictly speaking, there is still data to be gleaned from the relationships, even if the name of the relationship (or the data) is encrypted. For example:

Is there a way to encrypt or secure the relationships in a Neo4j database such that an attacker cannot glean the structure of the graph (even if the data itself is secure)?


Answer:

You can try to obfuscate your data structure by adding a reasonably large number of nonsense relationships (and possibly also nonsense nodes).

A nonsense relationship can have a nonsense type, or have a special property value that flags it as a nonsense relationship. The property used could even be a "real" property. And these nonsense relationships can be connected to both real and nonsense nodes.

A nonsense node can have a nonsense label, or have a special property value that flags it as a nonsense node.

Of course, your queries would have to be crafted to ignore the nonsense nodes and relationships, but that may not be difficult.

Question:

I am currently new to neo4j and exploring with cypher queries for a task at hand. I am using neo4j bolt driver in Java Here is what I am trying to achieve. I have something like below data available as a Java ArrayList(stored in a HashMap):

employerId 2 : [employeeId 1, employeeId 2, employeeId3, ...]

Which basically shows relationship between employer and employee (these are the employees of employer 2)

Now, I need to find these employees & employer in the graph(they may or may not exist already) and create a "(x:Employer) -[Employs]->(y:Employee)" relationship between them.

One way (maybe, naive) that I can think of, is to search for employer and employee every time and run a separate CREATE query for each.

match (employer:Employer{name:"John"}), (name:Employee{name:"Snow"}) CREATE (employer)-[pr:EMPLOYES]->(employee)

But I feel that it would unnecessary search for the same Employer node multiple times. And as time is an important criterion for me right now, I am looking for a better way(if exists)

As a newbie to neo4j, all I can think of is, to do a search for the employer ID once, and then run multiple queries using that result, with Employee ID being searched every time. But I am unable to find the correct query to do this. Moreover, will this be the right approach? I need to prepare this query from Java. So should I query multiple times or send a single query?


Answer:

This query below looks similar to the one from @Lju. However, it has a few improvements.

  1. The MERGE for the Employer only needs to be done once, so it should come before the UNWIND. Otherwise, it would be done for every Employee.
  2. You should pass the employer name (or id) and the list of employee names (or ids) in parameters. In the following example, the Cypher code refers to the parameters as $employerName and $names.
  3. Also, since the WITH clause between the 2 MERGE clauses was just passing all identifiers forward, it is not needed. (However, Cypher syntax does require a WITH clause between a MERGE and an UNWIND).

Query:

MERGE (employer:Employer {name: $employerName})
WITH employer
UNWIND $names AS name
MERGE (employee:Employee {name: name})
MERGE (employer)-[:Employs]->(employee)
RETURN *

Question:

I have the following classes:

@NodeEntity
public class Item{
  //...
}

@RelationshipEntity(type = "HAS")
public class HasRelation{
  //...
  @StartNode
  private User user;

  @EndNode
  private Item item;
}

@NodeEntity
public class User{
  //...
  @Relationship(type="HAS")
  private Set<HasRelation> has;
}

So now I have a User Sven with ID 1 having an Item Hammer in the Database and want to load it. When I call the OGM session.load(User.class, 1) I always get an Stackoverflow-Exception, because the User hold a Relationship, holding the User, holding a relationship, and so on. This feels like the wrong way to use OGM for me and I don't want to restrict the Depth by which I load to 0. However the OGM specification tells me, that there is no other way, since the RelationshipEntity needs a Start- and EndNode and has to be referenced in one of those. So I don't see a way to prevent this Exception other than resticting the Loading-Depth to 0. Is there a better way?


Answer:

You are exposing the data as JSON. The converter also needs to traverse the 'object tree' and this causes the stackoverflow.

The solution is simple: You are defining an outgoing relationship in the User class so this object does not need to be visited again when the jackson lib hits the relationship.

@RelationshipEntity(type = "LIKES")
public class LikedBook {

@Id
@GeneratedValue
private Long id;

private String how;

@StartNode
@JsonIgnore // <- "do not go back"
private User user;

@EndNode
private Book book;

Question:

Here is my User class:

@NodeEntity
public class User {

    @GraphId
    private Long id;

    @Property
    private String name;

    @Property
    private String email;

    @Property
    private String password;

    @Property
    private String photoLink;

    @Property
    private Integer age;

    @Property
    private Country country;

    @Property
    private Gender gender;

    @Property
    private String about;

    @Property
    private boolean online;

    private Collection<UserHasLanguage> hasLanguage;

    @Relationship(type="HAS_ROLE", direction=Relationship.OUTGOING)
    private Collection<Role> roles;

    @Relationship(type="HAS_IN_FRIENDLIST", direction=Relationship.OUTGOING)
    private Collection<User> friendList;

    @Relationship(type="HAS_IN_BLACKLIST", direction=Relationship.OUTGOING)
    private Collection<User> blackList;

So I want users to have one-side relationships HAS_IN_FRIENDLIST to other users. At the service level I have a method for adding friends to user:

public void addToFriendList (User whoAdds, User whoIsAdded)
        throws UserNotFoundException{
    if (whoIsAdded == null)
        throw new UserNotFoundException("User not found");
    Collection<User> friendList = whoAdds.getFriendList();
    if (friendList == null)
        friendList = new HashSet<>();
    friendList.add(whoIsAdded);
    whoAdds.setFriendList(friendList);
    userRepository.save(whoAdds);
}

However, when I use this method, some previous relationships "HAS_IN_FRIENDLIST" of this user are removed. I have noticed, that whoAdds.getFriendList() method always returns only 1 User.

How can I fix this?


Answer:

I can't test this, but my understanding is that Collection isn't supported. For a list, you must use one of these (as specified here)

java.util.Vector

java.util.List, backed by a java.util.ArrayList

java.util.SortedSet, backed by a java.util.TreeSet

java.util.Set, backed by a java.util.HashSet

Arrays

So change Collection<User> to Set<User>

Question:

Is CSV the only options to speed up my bulk relationships creation?

I read many articles in internet, and they all are telling about CSV. CSV will definitely give me a performance boost (could you suppose how big?), but I'm not sure I can store data in CSV format. Any other options? How much I will get from using Neo4J 3 BOLT protocol?

My program

I'm using Neo4j 2.1.7. I try to create about 50000 relationships at once. I execute queries in batch of size 10000, and it takes about 120-140 seconds to insert all 50000.

My query looks like:

MATCH (n),(m) 
WHERE id(n)=5948 and id(m)=8114 
CREATE (n)-[r:MY_REL {
    ID:"4611686018427387904",
    TYPE: "MY_REL_1"
    PROPERTY_1:"some_data_1",
    PROPERTY_2:"some_data_2",
    .........................
    PROPERTY_14:"some_data_14"
}]->(m) 
RETURN id(n),id(m),r

Answer:

As it is written in the documentation:

Cypher supports querying with parameters. This means developers don’t have to resort to string building to create a query. In addition to that, it also makes caching of execution plans much easier for Cypher.

So, you need pack your data as parameters and pass with cypher query:

UNWIND {rows} as row
MATCH (n),(m) 
WHERE id(n)=row.nid and id(m)=row.mid
CREATE (n)-[r:MY_REL {
    ID:row.relId,
    TYPE:row.relType,
    PROPERTY_1:row.someData_1,
    PROPERTY_2:row.someData_2,
    .........................
    PROPERTY_14:row.someData_14
}]->(m) 
RETURN id(n),id(m),r

Question:

I am using Java API for Neo4j embedded db.

While saving an instance as a Node and I would like to create a Relationship from it to another node, that I have no reference other than a property (id/key).

As I understand it, if I'd have the two nodes I would just use:

nodeBeingSavedSeparately.createRelationshipTo(
            nodeToHaveRelationshipTo,
            RELATIONSHIP_TYPE
);

But I am just adding a new node and I would like to have the relationship with another already existing node, not to create a new node.

Is it possible to get the right instance of Node from the database and use it in that method? Something like:

nodeBeingSavedSeparately.createRelationshipTo(
            getNodeByProperty("idPropertyOfTheNodeToHaveRelationshipTo"), 
            RELATIONSHIP_TYPE
);

I found out, that there is a getNodeById(long); method, but there is no Node.setId(long); method. How do I set/get the right reference for the relationship?


Answer:

You can use either findNode or findNodes in GraphDataBaseService to get the existing node.

Question:

I have the User entity; the user can be a member of multiple groups and be a member of one organization. There are several options to handle such relationships:

  1. Class User has fields Set<Group> groups and Organization organization
  2. Classes Group and Organization has fields Set<User> users
  3. Both options are used simultaneously (kind of bidirectional relationship)

In addition, there are annotations for relationships with specifying directions:

Spring Data Neo4j ensures by default that there is only one relationship of a given type between any two given entities. The exception to this rule is when a relationship is specified as either OUTGOING or INCOMING between two entities of the same type. In this case, it is possible to have two relationships of the given type between the two entities, one relationship in either direction.

If you don’t care about the direction then you can specify direction=Relationship.UNDIRECTED which will guarantee that the path between two node entities is navigable from either side.

Source: Good Relationships: The Spring Data Neo4j Guide Book

As soon as I want to be able to get the groups of user and users within the group as fast as I can, I finished with an approach using two options listed above at the same time as well as annotating every relationship as UNDIRECTED because it looks like the universal approach. Does it have any drawbacks? If so, which approach would be better?


Answer:

Since you want to retrieve groups for a user, and users in a group, it makes sense to set up your object model as you describe in #1 and #2.

UNDIRECTED is not a good choice here because it implies that the relationship between user and group can be in any direction, and I'm guessing you don't want this in your graph model. It's good for relationships where you don't care about the direction (such as (user1)-[:FRIEND]-(user2)) but not otherwise. I'd use OUTGOING and INCOMING in either class depending on what your relationship between user and group actually is.

Question:

I have two neo4j-OGM node entities connected with property-less relationship like so:

@NodeEntity
public class User {

    @Relationship(type = RelationshipNames.USER_DEVICES, direction = Relationship.UNDIRECTED)
    private Set<Device> devices;

}

@NodeEntity
public class Device {

    @Relationship(type = RelationshipNames.USER_DEVICES, direction = Relationship.UNDIRECTED)
    private User user;

}

When I add a device to a user and then perform save, i get this graph:

Later on, when i both remove the device from user device set and save it, and set device user to null and save it, I still have the same graph, meaning the relationship between the device and user still exist.

I'm i doing something wrong? Is there a way to delete it?


Answer:

Without seeing the code you've written that saves these objects, its not easy to diagnose your problem. However, I would suggest two things.

First, I would ensure that the addition and removal of user-device references in your domain model is managed by the domain model itself. In other words, provide a behaviour on the User class that keeps the Device object consistent, whenever a Device is added or removed.

addDevice(Device device) {
    if (device.user() != null) {
        device.user().removeDevice(device)
    }
    device.setUser(this)
    devices.add(device);
}

Obviously you'd need to write an equivalent removeDevice() as well. This will ensure both objects are properly sync'd if you manage them via the User. If you also intend managing them from the Device as well, you should write an equivalent updateUser() method on the Device class that achieves the same effect.

The point is: get your domain model to do this work. Its much easier to reason about (and test) and you don't need to keep calling getters and setters everywhere in your persistence code just to keep everything in sync.

If, after having made these changes it is still failing, then make the UNDIRECTED relationship INCOMING on one side and OUTGOING on the other (doesn't matter which). If that fixes the problem, it points to a possible bug in the OGM. If which case, please report it here!

Question:

I am new to Neo4J and working with Spring data repository. Following is the domain definition

@NodeEntity
public class Actor {
    Long id;
    private Set<Role> roles;
}

@RelationshipEntity(type="PLAYED_IN")
public class Role {
    @GraphId   private Long relationshipId;
    @Property  private String title;
    @StartNode private Actor actor;
    @EndNode   private Movie movie;
}

@NodeEntity
public class Movie {
    private Long id;
    private String title;
}

And have GraphRepository defined for each entity class Following code does not save the RelationshipEntity

Actor actor = new Actor("actorName");
actor = actorRepository.save(actor);

Movie movie = new Movie("movieTitle");
movie = movieRepository.save(movie);

Role role = new Role(actor, movie, "roleTitle");
role = roleRepository.save(role);

Do I have to annotate the roles variable in Actor class? Do I have to populate roles collection before saving Actor? If I do so then the properties on Role are not saved.


Answer:

Yes, you must annotate the roles in the Actor entity.

If you're using neo4j-ogm 1.1.3 or an earlier version, make sure that when you create the new role, you add this to the collection of roles in the Actor entity.

If you're using neo4j-ogm 1.1.4-SNAPSHOT, your code should work (after you annotate the roles)

Question:

I am following the Neo4j OGM guide at - http://neo4j.com/docs/ogm/java/stable/

For Relationship entities, we need to have a start node and an end node. I've modified the example a little bit (to make it simpler like this) -

@NodeEntity
public class Student extends Entity {
    private String name;

    @Relationship(type= "ENROLLED")
    private Enrollment enrollment = new Enrollment();

    public String getName() {
        return name;
    }

    public Enrollment getEnrollment() {
        return enrollment;
    }

    public void setEnrollment(Enrollment enrollment) {
        this.enrollment = enrollment;
    }

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }
}

@NodeEntity
public class Course extends Entity {
    private String name;

    public String getName() {
        return name;
    }

    @Relationship(type= "ENROLLED", direction= Relationship.INCOMING)
    private Enrollment enrollment = new Enrollment();

    public Enrollment getEnrollment() {
        return enrollment;
    }

    public void setEnrollment(Enrollment enrollment) {
        this.enrollment = enrollment;
    }

    public Course() {
    }

    public Course(String name) {
        this.name = name;
    }
}

@RelationshipEntity(type = "ENROLLED")
public class Enrollment extends Entity {
    @StartNode
    private Student student;

    @EndNode
    private Course course;

    private Date enrolledDate;

    public Student getStudent() {
        return student;
    }

    public Course getCourse() {
        return course;
    }

    public Date getEnrolledDate() {
        return enrolledDate;
    }

    public Enrollment() {
    }

    public Enrollment(Student student, Course course, Date enrolledDate) {
        this.student = student;
        this.course = course;
        this.enrolledDate = enrolledDate;
    }
}

Now when I try to save this in Neo4j, it works fine. However in my scenario, the type of StartNode and EndNode objects are the same -

@NodeEntity
public class MyObject extends Entity {
    private String name;

    @Relationship(type="CONNECTION")
    private MyConnection startConnection = new MyConnection();

    @Relationship(type="CONNECTION", direction= Relationship.INCOMING)
    private MyConnection endConnection = new MyConnection();

    public String getName() {
        return name;
    }

    public MyConnection getStartConnection() {
        return startConnection;
    }

    public void setStartConnection(MyConnection myConnection) {
        this.startConnection = myConnection;
    }

    public MyConnection getEndConnection() {
        return endConnection;
    }

    public void setEndConnection(MyConnection endConnection) {
        this.endConnection = endConnection;
    }

    public MyObject() {
        super();
    }

    public MyObject(String name) {
        super();
        this.name = name;
    }
}

@RelationshipEntity(type="CONNECTION")
public class MyConnection extends Entity {
    @StartNode
    private MyObject start;

    @EndNode
    private MyObject end;

    private String name;

    public String getName() {
        return name;
    }

    public MyConnection() {
        super();
    }

    public MyConnection(MyObject start, MyObject end, String name) {
        super();
        this.start = start;
        this.end = end;
        this.name = name;
    }
}

When I try to save these using -

public class Main {

    public static void main(String[] args) {
        Session session = Neo4jSessionFactory.getInstance().getNeo4jSession();

        Student s = new Student("manoj");
        Course c = new Course("physics");
        Enrollment e = new Enrollment(s, c, new Date());
        s.setEnrollment(e);
        c.setEnrollment(e);

        MyObject startObj = new MyObject("Start Object");
        MyObject endObj = new MyObject("End Object");
        MyConnection conn = new MyConnection(startObj, endObj, "Connection");
        startObj.setStartConnection(conn);
        endObj.setEndConnection(conn);

        try(Transaction tx = session.beginTransaction()) {
            session.save(s);
            session.save(c);
            session.save(e);

            session.save(startObj);
            session.save(endObj);
            session.save(conn);

            tx.commit();
        }
    }
}

The student, course and enrollments objects get saved, but the two MyObject and the MyConnection objects don't get saved and I get the following exception -

Exception in thread "main" java.lang.NullPointerException
    at org.neo4j.ogm.metadata.MetaData.classInfo(MetaData.java:76)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelationshipEntity(EntityGraphMapper.java:389)
    at org.neo4j.ogm.mapper.EntityGraphMapper.link(EntityGraphMapper.java:319)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntityReferences(EntityGraphMapper.java:262)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntity(EntityGraphMapper.java:154)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelatedEntity(EntityGraphMapper.java:528)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelationshipEntity(EntityGraphMapper.java:420)
    at org.neo4j.ogm.mapper.EntityGraphMapper.link(EntityGraphMapper.java:319)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntityReferences(EntityGraphMapper.java:262)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntity(EntityGraphMapper.java:154)
    at org.neo4j.ogm.mapper.EntityGraphMapper.map(EntityGraphMapper.java:87)
    at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:65)
    at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:41)
    at org.neo4j.ogm.session.Neo4jSession.save(Neo4jSession.java:370)
    at neo4j.ogm.ex.Main.main(Main.java:37)

Can you please help me to solve this - 1) Is it necessary that the StartNode and EndNode objects be of different types? 2) Is there some problem with my code or is it a shortcoming of the Neo4j OGM?

Thanks in advance,

Manoj.

Update after trying Luanne's suggestion -

Thanks Luanne. I tried your suggestion, though I had to specify the URL differently. I used - http://m2.neo4j.org/content/repositories/snapshots because by default it used https and I was getting some security exception and this dependency was not getting downloaded.

Anyways, with the 1.1.1-SNAPSHOT version, I still get the below error -

Exception in thread "main" java.lang.NullPointerException
    at org.neo4j.ogm.metadata.MetaData.classInfo(MetaData.java:80)
    at org.neo4j.ogm.mapper.EntityGraphMapper.haveRelationEndsChanged(EntityGraphMapper.java:391)
    at org.neo4j.ogm.mapper.EntityGraphMapper.getRelationshipBuilder(EntityGraphMapper.java:362)
    at org.neo4j.ogm.mapper.EntityGraphMapper.link(EntityGraphMapper.java:325)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntityReferences(EntityGraphMapper.java:276)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntity(EntityGraphMapper.java:157)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelatedEntity(EntityGraphMapper.java:571)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelationshipEntity(EntityGraphMapper.java:473)
    at org.neo4j.ogm.mapper.EntityGraphMapper.link(EntityGraphMapper.java:329)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntityReferences(EntityGraphMapper.java:276)
    at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntity(EntityGraphMapper.java:157)
    at org.neo4j.ogm.mapper.EntityGraphMapper.map(EntityGraphMapper.java:90)
    at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:67)
    at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:43)
    at org.neo4j.ogm.session.Neo4jSession.save(Neo4jSession.java:376)
    at neo4j.ogm.ex.Main.main(Main.java:37)

Answer:

Is your intent to model MyObject to contain a single outgoing relationship type CONNECTION and a single incoming relationship type CONNECTION where each relationship has a property name?

So if we're looking at EndObject, then startConnection is the relationship with name conn2 and endConnection is the relationship with name conn1?

If so, we might have a problem here. https://jira.spring.io/browse/DATAGRAPH-728

Update: This wasn't a bug after all. The problem is the initialization you have in MyObject:

@Relationship(type="CONNECTION")
private MyConnection startConnection = new MyConnection();

@Relationship(type="CONNECTION", direction= Relationship.INCOMING)
private MyConnection endConnection = new MyConnection();

You've initialized both startConnection and endConnection to an invalid relationship entity i.e. one with no start and no end node.

In your test, you set the startConnection on startObj but not the end. In effect, the endConnection is represented by the invalid relationship entity.

Remove the initialization and it should work as you expect.

Question:

I'm using the Neo4J API. I created the following relationship between two nodes:

node1.createRelationshipTo(graphDb.getNodeById(idNode2), new RelationshipType() {
    @Override
    public String name() {
        return "CONECTED";
    }
});

How do I set a property for this relationship?


Answer:

That call you're doing there returns a Relationship object. You can see the javadocs for that here.

Relationship objects and Node objects both implement PropertyContainer. So you just use the setProperty() method that's implemented from PropertyContainer in the Relationship class.

Relationship r = node1.createRelationshipTo(graphDb.getNodeById(idNode2), new RelationshipType() {
    @Override
    public String name() {
        return "CONECTED";
    }
});

r.setProperty("PropertyName", "PropertyValue");

Question:

For sure, a simple question but I can't find my answer. How can i get the entities from relationships using Neo4JRepository ?

Java 8 // Spring Boot 2 // SDN 5.0.9 // OGM 3

There is my code:

@NodeEntity(label = "category")
public class Category {

    private @Id @GeneratedValue(strategy = InternalIdStrategy.class) Long id;
    private String name;
    @Relationship(type = "MEMBER_OF", direction = Relationship.INCOMING)
    private Set<Sport> sports;
}

@NodeEntity(label = "sport")
public class Sport {

    private @Id @GeneratedValue(strategy = InternalIdStrategy.class) Long id;
    private String name;
    private String descrition;
    @Relationship(type = "MEMBER_OF")
    private Set<Category> categories;
}
@RelationshipEntity(type = "MEMBER_OF")
public class Membership {
    @Id
    @GeneratedValue
    private Long id;
    @StartNode
    Sport sport;
    @EndNode
    Category category;
}

A simple findAll from my Neo4jRepository return all nodes Sport but the set categories is null

So, can you tell me what did I wrong ?

Thanks.


Edit 21/08/2018

I changed my classes like this:

@NodeEntity(label = "sport")
public class Sport {

    private @Id @GeneratedValue(strategy = InternalIdStrategy.class) Long id;
    private String name;
    private String descrition;
    private Set<Membership> memberships;
}
@NodeEntity(label = "category")
public class Category {

    private @Id @GeneratedValue(strategy = InternalIdStrategy.class) Long id;
    private String name;
    private Set<Membership> memberships;
}
@RelationshipEntity(type = "MEMBER_OF")
public class Membership {
    @Id
    @GeneratedValue
    private Long id;
    @StartNode
    Sport sport;
    @EndNode
    Category category;
}

Now i've got this result:

  • In neo4j browser, the relationship is called merberships. Why OGM didn't use the RelationshipEntity's type ?
  • In my Rest service, using findAll, i still get null on this set.

Nope, it's ok here :) I've just forgot to keep @Relationship on my nodes

Another Question: How do I work with this Optional given by Neo4jRepository.findById, did someone have a good article for me ?


Answer:

You are declaring a direct relationship between Category and Sport called MEMBER_OF but also define a rich relationship (@RelationshipEntity) with the same name. Neo4j-OGM does not know what to map in this case.

From what I see in the sample code it is not necessary to add the rich relationship class at all because there are no additional properties defined and this would be the only reason to create such a class.

If you have properties defined but just not listed in the example, you should change the type of your collections and set it to Membership in both classes.