I have a Customer node class like this:
#Node
public class Customer extends BaseGraphEntity {
private int customerId;
private String name;
private double size;
#Relationship(type = "TRAN",direction = Relationship.Direction.OUTGOING)
public Set<Transaction> transactions;
public void inTran(Customer target, int year, double amount) {
if (transactions == null) {
transactions = new HashSet<>();
}
transactions.add(new Transaction().withAmount(amount).withTarget(target).withYear(year));
}
}
And a relationship entity Transaction as:
#RelationshipProperties
public class Transaction extends BaseGraphEntity {
private double amount;
private int year;
#TargetNode
private Customer target;
}
This is my repository method:
public interface GraphRepository extends Neo4jRepository<Customer, Long>{
#Query("MATCH (o:Customer{customerId: $customerId})-[t:TRAN]->(c) where t.year = $year return o,t,c")
Customer getRelationsOfACustomer(int customerId, int year);
}
I checked the query using neo4j client and it returns what I want as shown in image(C1 is main customer):
However, I want to cast this result in a singe Customer node with 2 elements in its "transactions" set and I want to keep all properties of Transaction class in the elements of set. But when I run it asks me to convert result into list of customers and when I do I lost transaction properties.
Is it possible to store result in single Customer instance with transactions? If so, how can I do this? I am open for all suggestions. Thanks for any help.
1. Problem summary
When I update an attribute of a single, existing Neo4j RelationshipEntity, the actual values of various other rich relationships of same type are suddenly replaced by their old, former status of values. It seems as if a transaction is rolled back. My expectation is that only the attribute of the RelationshipEntity under inspection is updated and all other relationships are untouched.
2. Initial situation
running with Neo4j 3.4.7, Spring Boot v2.0.5.RELEASE, Spring v5.0.9.RELEASE
no explicit usage of transactions
reduced graph schema:
3. Target-actual comparison
3.1. Chronological sequence
The simplified use case according the graph schema can be summarized as following:
Identify all ClassC nodes
For them find various related ClassB (via ClassA)
For each identified ClassB create a ClassD including relationship to ClassB and a rich relationship CDMapping to ClassC)
3.2 Expected / positive result
The complete described block works fine for the first run. Various RelationshipEntitys between ClassC and ClassD with the attribute "Default Value" are setup, the last rich relationship receives intended the value "Special Value".
3.3 Detailed problem
When it comes to the second block cycle – exactly by saving the first new RelationshipEntity between ClassC and ClassD - the attributes of the previous run are unexpected set back to "Default Value", replacing the original "Special Value".
3.4 Neo4j generated queries
The following queries are executed by Neo4j in this moment, triggered by the relevant cdMappingDAO.save(cdMapping);:
UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId CREATE (startNode)-[rel:`MAPS_TO`]->(endNode) SET rel += row.props RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=91, relRef=-45, endNodeId=115, props={attribute=Default Value}}]}
UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId MERGE (startNode)-[rel:`CONTAINS`]->(endNode) RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=88, relRef=-49, endNodeId=115, props={}}, {startNodeId=92, relRef=-51, endNodeId=91, props={}}, {startNodeId=88, relRef=-54, endNodeId=93, props={}}, {startNodeId=89, relRef=-56, endNodeId=94, props={}}, {startNodeId=92, relRef=-57, endNodeId=90, props={}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassA`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=92, props={name=Class A}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassB`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=88, props={name=Class B1}}, {nodeId=89, props={name=Class B2}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassD`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=115, props={}}, {nodeId=93, props={}}, {nodeId=94, props={}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassC`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=90, props={name=Class C1}}, {nodeId=91, props={name=Class C2}}]}
UNWIND {rows} AS row MATCH ()-[r]-() WHERE ID(r) = row.relId SET r += row.props RETURN ID(r) as ref, ID(r) as id, {type} as type with params {rows=[{relId=104, props={attribute=Default Value}}, {relId=106, props={attribute=Default Value}}], type=rel}
4. To be solved challenge
Can you please give me an advice why and by what component the attribute values are reset? How can I ensure, that the RelationshipEntity under inspection is updated only? Many thanks in advance for pointing me into the right direction!
5. Code snippets
5.1 GraphHandler
#Component
public class GraphHandler implements CommandLineRunner {
private ClassADAO classADAO;
private ClassBDAO classBDAO;
private ClassCDAO classCDAO;
private ClassDDAO classDDAO;
private CDMappingDAO cdMappingDAO;
private SessionFactory sessionFactory;
#Autowired
public GraphHandler(ClassADAO classADAO, ClassBDAO classBDAO, ClassCDAO classCDAO, ClassDDAO classDDAO, CDMappingDAO cdMappingDAO, SessionFactory sessionFactory) {
this.classADAO = classADAO;
this.classBDAO = classBDAO;
this.classCDAO = classCDAO;
this.classDDAO = classDDAO;
this.cdMappingDAO = cdMappingDAO;
this.sessionFactory = sessionFactory;
}
public void run(String... args) {
createInitialModel();
runUseCase();
}
private void createInitialModel() {
ClassA classA = new ClassA("Class A");
ClassB classB1 = new ClassB("Class B1");
ClassB classB2 = new ClassB("Class B2");
ClassC classC1 = new ClassC("Class C1");
ClassC classC2 = new ClassC("Class C2");
classA.getClassBs().addAll(Arrays.asList(classB1, classB2));
classA.getClassCs().addAll(Arrays.asList(classC1, classC2));
classADAO.save(classA);
classBDAO.save(classB1);
classBDAO.save(classB2);
classCDAO.save(classC1);
classCDAO.save(classC2);
}
private void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship);
}
}
}
5.2 CDMapping
#RelationshipEntity(type = "MAPS_TO")
public class CDMapping {
#Id
#GeneratedValue
private Long id;
#StartNode
private ClassC classC;
#EndNode
private ClassD classD;
private String attribute;
public CDMapping(ClassC classC, ClassD classD, String attribute) {
this.classC = classC;
this.classD = classD;
this.attribute = attribute;
classC.getCdMappings().add(this);
classD.getCdMappings().add(this);
}
// default constructor, getter and setter here
}
5.3 ClassA
#NodeEntity
public class ClassA extends Entity {
private String name;
#Relationship(type = "CONTAINS")
private List<ClassC> classCs = new ArrayList<>();
#Relationship(type = "MAPS_TO")
private List<ClassB> classBs = new ArrayList<>();
// default constructor, getter and setter here
}
5.4 ClassB
#NodeEntity
public class ClassB extends Entity {
private String name;
#Relationship(type = "MAPS_TO", direction = Relationship.INCOMING)
private List<ClassA> classAs = new ArrayList<>();
#Relationship(type = "CONTAINS")
private List<ClassD> classDs = new ArrayList<>();
// default constructor, getter and setter here
}
5.5 ClassC
#NodeEntity
public class ClassC extends Entity {
private String name;
#Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
private ClassA classA;
#Relationship(type = "MAPS_TO")
private List<CDMapping> cdMappings = new ArrayList<>();
// default constructor, getter and setter here
}
5.6 ClassD
#NodeEntity
public class ClassD extends Entity {
#Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
private ClassB classB;
#Relationship(type = "MAPS_TO", direction = Relationship.INCOMING)
private List<CDMapping> cdMappings = new ArrayList<>();
// default constructor, getter and setter here
}
Update
5.7 CDMappingDAO
#Repository
public interface CDMappingDAO extends Neo4jRepository<CDMapping, Long> {
#Query("MATCH (classC:ClassC)-[relationship:MAPS_TO]-(classD:ClassD) WHERE id(classC)={classCId} AND id(classD)={classDId} RETURN classC, relationship, classD;")
CDMapping getRelationshipByClassCAndClassD(#Param("classCId") Long classCId, #Param("classDId") Long classDId);
}
5.8 ClassADAO / ClassCDAO / ClassDDAO
#Repository
public interface ClassADAO extends Neo4jRepository<ClassA, Long> {
}
Except for the first Neo4jRepository type the ClassCDAO and ClassDDAO are identical.
5.9 ClassBDAO
#Repository
public interface ClassBDAO extends Neo4jRepository<ClassB, Long> {
#Query("MATCH (classC:ClassC)<-[:CONTAINS]-(:ClassA)-[:MAPS_TO]->(classB:ClassB) WHERE id(classC)={classCId} RETURN classB;")
List<ClassB> findClassBSelection(#Param("classCId") Long classCId);
}
At the moment it seems there's a bug somewhere in OGM, but I have found two workarounds for your problem.
Workaround one leaves your code pretty much as it is:
Change runUseCase One is in your GraphHandler to save the CDMapping with depth 0:
private void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping, 0); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship, 0);
}
}
This leaves the "special" ones intact.
I'd rather suggest the following change, adding some more correct transaction boundaries.
Introduce a service class like SomeGraphBasedService. A dedicated class is necessary due to the fact how Springs declarative transactions work. Both methods, createInitialModel and runUseCase span now one transaction each and all DAO methods participate in it. Please take especially note on the comment, I only save the topmost parent class in createInitialModel:
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
public class SomeGraphBasedService {
private final ClassADAO classADAO;
private final ClassBDAO classBDAO;
private final ClassCDAO classCDAO;
private final ClassDDAO classDDAO;
private final CDMappingDAO cdMappingDAO;
public SomeGraphBasedService(ClassADAO classADAO, ClassBDAO classBDAO,
ClassCDAO classCDAO, ClassDDAO classDDAO, CDMappingDAO cdMappingDAO) {
this.classADAO = classADAO;
this.classBDAO = classBDAO;
this.classCDAO = classCDAO;
this.classDDAO = classDDAO;
this.cdMappingDAO = cdMappingDAO;
}
#Transactional
public void createInitialModel() {
ClassA classA = new ClassA("Class A");
ClassB classB1 = new ClassB("Class B1");
ClassB classB2 = new ClassB("Class B2");
ClassC classC1 = new ClassC("Class C1");
ClassC classC2 = new ClassC("Class C2");
classA.getClassBs().addAll(Arrays.asList(classB1, classB2));
classB1.getClassAs().add(classA);
classB2.getClassAs().add(classA);
classA.getClassCs().addAll(Arrays.asList(classC1, classC2));
classADAO.save(classA);
// No need to save them one by one, the releationships
// take care of that while saving the parent class at the top
/*
classBDAO.save(classB1);
classBDAO.save(classB2);
classCDAO.save(classC1);
classCDAO.save(classC2);
*/
}
#Transactional
public void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO
.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship);
}
Iterable<CDMapping> f = cdMappingDAO.findAll();
for (CDMapping ff : f) {
System.out.println(ff);
}
}
}
Then your command line runner becomes this:
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
#Component
public class GraphHandler implements CommandLineRunner {
private final SomeGraphBasedService someGraphBasedService;
public GraphHandler(SomeGraphBasedService someGraphBasedService) {
this.someGraphBasedService = someGraphBasedService;
}
public void run(String... args) {
this.someGraphBasedService.createInitialModel();
this.someGraphBasedService.runUseCase();
}
}
While the first one is obviously a workaround, the other solution is the one I'd prefer in a real world scenario.
Anyway, the output of both is now as expected:
CDMapping{id=21, classC=ClassC{name='Class C1'}, classD=ClassD{classB=ClassB{name='Class B1'}}, attribute='Default Value'}
CDMapping{id=23, classC=ClassC{name='Class C1'}, classD=ClassD{classB=ClassB{name='Class B2'}}, attribute='Special Value'}
CDMapping{id=25, classC=ClassC{name='Class C2'}, classD=ClassD{classB=ClassB{name='Class B1'}}, attribute='Default Value'}
CDMapping{id=27, classC=ClassC{name='Class C2'}, classD=ClassD{classB=ClassB{name='Class B2'}}, attribute='Special Value'}
In My Neo4j 3.0.3 Spring Data Neo4j 4.2.0.SNAPSHOT project I have a following entities:
#NodeEntity
public abstract class BaseEntity {
#GraphId
private Long id;
private Date createDate;
private Date updateDate;
...
}
#NodeEntity
public class Decision extends BaseEntity {
private final static String CONTAINS = "CONTAINS";
private final static String DEFINED_BY = "DEFINED_BY";
private String name;
#Relationship(type = CONTAINS, direction = Relationship.INCOMING)
private Set<Decision> parentDecisions = new HashSet<>();
#Relationship(type = CONTAINS, direction = Relationship.OUTGOING)
private Set<Decision> childDecisions = new HashSet<>();
#Relationship(type = DEFINED_BY, direction = Relationship.INCOMING)
private Set<CriterionGroup> criterionGroups = new HashSet<>();
#Relationship(type = DEFINED_BY, direction = Relationship.INCOMING)
private Set<Criterion> criteria = new HashSet<>();
...
}
#NodeEntity
public class Criterion extends BaseEntity {
private final static String CONTAINS = "CONTAINS";
private final static String DEFINED_BY = "DEFINED_BY";
private String name;
#Relationship(type = CONTAINS, direction = Relationship.INCOMING)
private CriterionGroup group;
#Relationship(type = DEFINED_BY, direction = Relationship.OUTGOING)
private Decision owner;
...
}
#NodeEntity
public class Vote extends Authorable {
private final static String CONTAINS = "CONTAINS";
#Relationship(type = CONTAINS, direction = Relationship.INCOMING)
private VoteGroup group;
private double weight;
private String description;
...
}
#NodeEntity
public class VoteGroup extends BaseEntity {
private static final String VOTED_ON = "VOTED_ON";
private final static String VOTED_FOR = "VOTED_FOR";
private final static String CONTAINS = "CONTAINS";
#Relationship(type = VOTED_FOR, direction = Relationship.OUTGOING)
private Decision decision;
#Relationship(type = VOTED_ON, direction = Relationship.OUTGOING)
private Criterion criterion;
#Relationship(type = CONTAINS, direction = Relationship.OUTGOING)
private Set<Vote> votes = new HashSet<>();
private double avgVotesWeight;
private long totalVotesCount;
...
}
I have a test method that creates a test nodes in Embeded Ne04j Database. Right now after porting my code from Neo4j 2x to 3x and SDN3x to SDN4x I see a noticeable performance degradation during these nodes creation.
I'm more than sure this is related to some issues in my implementation and not to technology itself so I'd like to ask to help me find the reasons of a slow performance.
For example I have a following method:
#Override
public Vote createVote(Decision decision, Criterion criterion, User author, String description, double weight) {
VoteGroup voteGroup = getVoteGroupForDecisionOnCriterion(decision.getId(), criterion.getId());
if (voteGroup == null) {
voteGroup = new VoteGroup(decision, criterion, weight, 1);
} else {
long newTotalVotesCount = voteGroup.getTotalVotesCount() + 1;
double newAvgVotesWeight = (voteGroup.getAvgVotesWeight() * voteGroup.getTotalVotesCount() + weight) / newTotalVotesCount;
voteGroup.setAvgVotesWeight(newAvgVotesWeight);
voteGroup.setTotalVotesCount(newTotalVotesCount);
}
return voteRepository.save(new Vote(voteGroup, author, weight, description));
}
constructors:
public VoteGroup(Decision decision, Criterion criterion, double avgVotesWeight, long totalVotesCount) {
this.decision = decision;
this.criterion = criterion;
this.avgVotesWeight = avgVotesWeight;
this.totalVotesCount = totalVotesCount;
}
public Vote(VoteGroup group, User author, double weight, String description) {
this.group = group;
group.addVote(this);
setAuthor(author);
this.weight = weight;
this.description = description;
}
and repository method:
#Query("MATCH (d:Decision)<-[:VOTED_FOR]-(vg:VoteGroup)-[:VOTED_ON]->(c:Criterion) WHERE id(d) = {decisionId} AND id(c) = {criterionId} RETURN vg")
VoteGroup getVoteGroupForDecisionOnCriterion(#Param("decisionId") Long decisionId, #Param("criterionId") Long criterionId);
Right now the performance of createVote method significantly degrades with a growth of amount of nodes.
The same code on Neo4j 2x and SDN3 worked much more faster. What can be wrong and how to fix the issue with performance?
UPDATED
This is profiler information:
Looks like methods:
CypherContext.deregisterOutgoingRelationships()
CypherContext.deregisterIncomingRelationships()
Right now I have 22397 total nodes at database:
845 Decisions
2274 Criterion
9387 Vote
9387 VoteGroup
voteDao.createVote method executes 4194 - 8194 ms.
For example on a very small database it starts working with a following time:
getVoteGroupForDecisionOnCriterion - 0-1ms
voteRepository.save - 9ms
and with a database growth getVoteGroupForDecisionOnCriterion works with a stable 0-1ms but performance of voteRepository.save very quickly significantly degrades.
So, looks like the bottleneck is voteRepository.save method.
This is my Neo4jConfig:
#Configuration
#EnableNeo4jRepositories(basePackages = "com.example")
#EnableTransactionManagement
public class Neo4jConfig extends Neo4jConfiguration {
#Override
public SessionFactory getSessionFactory() {
return new SessionFactory("com.example");
}
}
ogm.properties:
driver=org.neo4j.ogm.drivers.bolt.driver.BoltDriver
URI=bolt://neo4j:password#localhost
What can be wrong and how to fix it ?
I have a following Spring Data Neo4j 4.2.0.BUILD-SNAPSHOT entities:
#NodeEntity
public class VoteGroup extends BaseEntity {
private static final String VOTED_ON = "VOTED_ON";
private final static String VOTED_FOR = "VOTED_FOR";
#Relationship(type = VOTED_FOR, direction = Relationship.OUTGOING)
private Decision decision;
#Relationship(type = VOTED_ON, direction = Relationship.OUTGOING)
private Criterion criterion;
...
}
#NodeEntity
public class Decision extends Commentable {
#Relationship(type = VOTED_FOR, direction = Relationship.INCOMING)
private Set<VoteGroup> voteGroups = new HashSet<>();
...
}
#NodeEntity
public class Criterion extends Authorable {
#Relationship(type = VOTED_ON, direction = Relationship.INCOMING)
private Set<VoteGroup> voteGroups = new HashSet<>();
....
}
repository:
#Repository
public interface VoteGroupRepository extends GraphRepository<VoteGroup> {
#Query("MATCH (d:Decision)<-[:VOTED_FOR]-(vg:VoteGroup)-[:VOTED_ON]->(c:Criterion) WHERE id(d) = {decisionId} AND id(c) = {criterionId} RETURN vg")
VoteGroup getVoteGroupForDecisionOnCriterion(#Param("decisionId") Long decisionId, #Param("criterionId") Long criterionId);
}
I'm creating VoteGroup with a following constructor:
public VoteGroup(Decision decision, Criterion criterion, double avgVotesWeight, long totalVotesCount) {
this.decision = decision;
decision.addVoteGroup(this);
this.criterion = criterion;
criterion.addVoteGroup(this);
this.avgVotesWeight = avgVotesWeight;
this.totalVotesCount = totalVotesCount;
}
but when I try to find previously saved VoteGroup with:
VoteGroup voteGroup = getVoteGroupForDecisionOnCriterion(decision.getId(), criterion.getId());
my voteGroup.decision and voteGroup.criterion are NULL..
but if I call right after that findOne method:
voteGroup = voteGroupRepository.findOne(voteGroup.getId());
voteGroup.decision and voteGroup.criterion are populated properly.
What is wrong with my repository method/Cypher and how to fix it ?
The query
#Query("MATCH (d:Decision)<-[:VOTED_FOR]-(vg:VoteGroup)-[:VOTED_ON]->(c:Criterion) WHERE id(d) = {decisionId} AND id(c) = {criterionId} RETURN vg")
returns only the VoteGroup node and so, this is all that the OGM can map.
If you want the decision and criterion too, then you have to return those nodes and their relationships. Something like this should work:
#Query("MATCH (d:Decision)<-[for:VOTED_FOR]-(vg:VoteGroup)-[on:VOTED_ON]->(c:Criterion) WHERE id(d) = {decisionId} AND id(c) = {criterionId} RETURN vg,d,for,on,c")
This blog post contains more examples: http://graphaware.com/neo4j/2016/04/06/mapping-query-entities-sdn.html
BTW the VoteGroup constructor you shared will not be used by the OGM, you need a no-args constructor.
Is is possible to pls advice me how to go about.. doing a Same Entity Relationship..
For ex.
Entity(class Person) relatesTo Entity(class Person).
CODE:
#NodeEntity
public class Person
{
#GraphId #GeneratedValue
private Long id;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "searchByPersonName")
private String personName;
#Fetch #RelatedTo(type = "CONNECTS_TO", direction = Direction.BOTH)
private Set<ConnectedPersons> connectedPersons;
public ConnectedPersons connectsTo(Person endPerson, String connectionProperty)
{
ConnectedPersons connectedPersons = new ConnectedPersons(this, endPerson, connectionProperty);
this.connectedPersons.add(connectedPersons); //Null Pointer Here(connectedPersons is null)
return connectedPersons;
}
}
CODE:
#RelationshipEntity(type = "CONNECTED_TO")
public class ConnectedPersons{
#GraphId private Long id;
#StartNode private Person startPerson;
#EndNode private Person endPerson;
private String connectionProperty;
public ConnectedPersons() { }
public ConnectedPersons(Person startPerson, Person endPerson, String connectionProperty) { this.startPerson = startPerson; this.endPerson = endPerson; this.connectionProperty = connectionProperty;
}
I am trying to have a Relationship to the same class.. i.e. Persons connected to the Person.. When I invoke a Junit test :
Person one = new Person ("One");
Person two = new Person ("Two");
personService.save(one); //Works also when I use template.save(one)
personService.save(two);
Iterable<Person> persons = personService.findAll();
for (Person person: persons) {
System.out.println("Person Name : "+person.getPersonName());
}
one.connectsTo(two, "Sample Connection");
template.save(one);
I get Null pointer when I try to do one.connectsTo(two, "Prop");
Please could you tell where am I going wrong?
Thanks in advance.
One other thing besides the missing initialization of the Set is that the class ConnectedPersons is a #RelationshipEntity. But in your class Person you are using it with the #RelatedTo annotation as if it were a #NodeEntity. You should use the #RelatedToVia annotation in the Person class instead.
You are getting null pointer exception in the below code because you haven't initialized the connectedPersons collection.
this.connectedPersons.add(connectedPersons); //Null Pointer Here(connectedPersons is null)
Initialize the collection as shown below
#Fetch #RelatedTo(type = "CONNECTS_TO", direction = Direction.BOTH)
private Set<ConnectedPersons> connectedPersons=new HashSet<ConnectedPersons>();