Wolfgang-Schuetzelhofer / jcypher

Java access to Neo4J graph databases at multiple levels of abstraction
Apache License 2.0
86 stars 15 forks source link

Create a relation between two nodes #38

Closed yishais closed 6 years ago

yishais commented 6 years ago

Hi

I have two nodes in the DB, Person & Event. I want to create a relation between them (such as Person attends Event). I can get the both as POJOs but that will not help me. I assume I need to get them as JcNode. I have created them using List<JcError> errors = domainAccess.store (this); So i assume they don't have an ID (or i am not sure where do I set one). I can retrieve them using value, such as:

            DomainObjectMatch<Person> usersMatch = query.createMatch(Person.class);
            query.WHERE(usersMatch.atttribute("email")).EQUALS(email);
        DomainQueryResult queryResult = query.execute();
        List<Person> persons = queryResult.resultOf (usersMatch);

But then I have a list of Person, not a JcNode.

How to I get a the two JcNodes (Person and Event) and define a JcRelation between them? How will I set it a node ID if I create the nodes from POJO's as above and not directly in neo4j? Do you have a complete example that shows how I retrieve a node and set a value in it, or a pointer to where to look at the documentation examples?

Thanks

-Yishai

Wolfgang-Schuetzelhofer commented 6 years ago

Hi, with JCypher you can work at different levels of abstraction. Your sample shows, that you are working at the topmost level, using (business)domains and domain objects (POJOS). They are automatically mapped to the graph database. At that level you don't directly handle nodes and relations in the database. Instead for your example, the class Person could have as an attribute a list of Events: private List<Event> events. If you then add an Event to this list and again store the Person object, Person and Event will automatically be related in the database.

Alternatively, if you work at the level of the Query DSL you are directly creating nodes and relations in the database (JcNode, JcRelation, ...). But then you have to manually map your domain objects (Person, Event, ...) and their respective attributes to nodes and relations in the DB.

If you want to send me some code and explain more specifically what you want to achieve, I can provide some solution code for you.

I hope the above explanations help so far.

Best regards, Wolfgang

yishais commented 6 years ago

Hi Wolfgang

Thanks for your answer. This was actualy the answer I hoped for. The topmost level of abstraction is what I wanted to be in. Saving into existing (retrieved) objects, or creating a relationship by pointing (for a lack of a better word) to other existing objects is what I hoped it does, and I think that should be enough for my needs. This is going to save me allot of time.

Thanks again

-Yishai

yishais commented 6 years ago

Hi Wolfgang

So I guess I am doing something wrong. I run this code

public class TestDB
    {
    public TestDB()
        {
        runTest (); 
        }

    public void runTest ()
        {
        Neo4jDatabase db = Neo4jDatabase.factoryMethod();
        IDBAccess dbAccess = db.getIDBAccess ();

        User user = new User();
        for (int i = 0; i < 5; i++)
            {
            user.setCounter (i);
            IDomainAccess domainAccess = DomainAccessFactory.createDomainAccess(dbAccess, "user-domain", DomainLabelUse.ALWAYS);
            List<JcError> errors = domainAccess.store (user);
            }
        }
    }

class User
    {
    int counter;

    public User () {}
    public void setCounter (int c) {counter = c;}
    }

Expecting at the end to have one User record with a counter==4. Instead, the DB contains 5 records, so every store generated a new record:

MATCH (n:User) RETURN n LIMIT 25

{"_c_version_":0,"counter":0}
{"_c_version_":0,"counter":1}
{"_c_version_":0,"counter":2}
{"_c_version_":0,"counter":3}
{"_c_version_":0,"counter":4}

What am I missing if I want to change the value of a filed inside the user record in the database?

Thanks

-Yishai

Wolfgang-Schuetzelhofer commented 6 years ago

Hi,

in every run of your loop you are creating a new instance of IDomainAccess. You should maintain only one instance of IDomainAccess. Create it outside your loop, just like IDBAccess.

Explanation: The IDomainAccess instance maintains state and synchronicity of your domain objects with the DB. If you instantiate a new IDomainAccess, and you want to change already persisted domain objects, you first have to load them by means of a query. Then they are known to the IDomainAccess instance, and changes are updated correctly.

I hope that helps, best regards, Wolfgang

yishais commented 6 years ago

Makes sense ;-) Thanks -Yishai

maryjis commented 6 years ago

Hello! I have a problem with creating a relation between two nodes. I create two nodes, for example Animal and Dog, and after creating I want to get them and create relationship between them. In my case, i added new property idKB. I find GrNode by this property. In result I have got an error.

    public GrNode get(String id) {
        JcNode object = new JcNode(projectName);
        JcQuery query = new JcQuery();
        query.setClauses(new IClause[]{
                MATCH.node(object).property("idKB").value(id),
                RETURN.value(object)
        });
        JcQueryResult result = dbAccess.execute(query);
        List<GrNode> objects = result.resultOf(object);
        if (objects.size() == 1) {
            return objects.get(0);
        }
        if (objects.size() >= 2) {
            throw new IndexOutOfBoundsException();
        }
        return null;
    }

    public GrRelation createConnection(String type, String startId, String endId) {

        GrNode startNode = this.get(startId);
        GrNode endNode = this.get(endId);
        GrRelation grRelation = graph.createRelation(type, startNode, endNode);
        List<JcError> errors = graph.store();
        if (!errors.isEmpty()) {
            return null;
        }

        return grRelation;

    }

        classKBDAO.createConnection("IS_SUBCLASS_OF", "c424816c-b03f-486a-8286-583f8110dcc3", "3849fe9a-681a-4b3d-87e9-11a8f3905deb");

Exception in thread "main" java.lang.NullPointerException at iot.jcypher.query.result.util.JSONContentHandler.getColumns(JSONContentHandler.java:61) at iot.jcypher.query.result.util.ResultHandler.getUnresolvedColumns(ResultHandler.java:538) at iot.jcypher.query.result.util.ResultHandler.getNode(ResultHandler.java:378) at iot.jcypher.query.result.util.ResultHandler.getNode(ResultHandler.java:367) at iot.jcypher.graph.GrRelation.getStartNode(GrRelation.java:44) at iot.jcypher.query.result.util.ResultHandler$QueryBuilder.addCreateRelationClause(ResultHandler.java:1064) at iot.jcypher.query.result.util.ResultHandler$QueryBuilder.buildCreateQuery(ResultHandler.java:964) at iot.jcypher.query.result.util.ResultHandler.createUpdateQueries(ResultHandler.java:594)

Wolfgang-Schuetzelhofer commented 6 years ago

Hi, sorry for taking me so long to return to you. Your problem can be solved with just one query:

JcNode animal = new JcNode("a");
JcNode dog = new JcNode("d");
JcRelation animal_to_dog = new JcRelation("a2d");
clauses = new IClause[]{
            MATCH.node(animal).property("idKB").value(id1),
            MATCH.node(dog).property("idKB").value(id2),
            CREATE_UNIQUE.node(animal)
                .relation(animal_to_dog).type("A2D").node(dog),
            RETURN.value(animal_to_dog)
    };

best regards, Wolfgang