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 ?
Related
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.
In my Neo4j 3.0.1 and SDN 4.1.1.RELEASE project I have a following entities:
#NodeEntity
public class CriterionGroup extends Authorable {
private final static String DEFINED_BY = "DEFINED_BY";
private final static String CONTAINS = "CONTAINS";
private String name;
private String description;
#Relationship(type = DEFINED_BY, direction = Relationship.OUTGOING)
private Decision owner;
#Relationship(type = CONTAINS, direction = Relationship.OUTGOING)
private Set<Criterion> criteria = new HashSet<>();
....
#NodeEntity
public class Criterion extends Authorable {
private final static String CONTAINS = "CONTAINS";
private final static String DEFINED_BY = "DEFINED_BY";
private String name;
private String description;
#Relationship(type = CONTAINS, direction = Relationship.INCOMING)
private CriterionGroup group;
#Relationship(type = DEFINED_BY, direction = Relationship.OUTGOING)
private Decision owner;
....
#NodeEntity
public class Decision extends Commentable {
private final static String CONTAINS = "CONTAINS";
private final static String DEFINED_BY = "DEFINED_BY";
private final static String VOTED_FOR = "VOTED_FOR";
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<>();
....
In my test I'm trying to delete CriterionGroup with a following repository method:
#Query("MATCH ()-[r]-(cg:CriterionGroup) WHERE id(cg) = {criterionGroupId} DELETE cg, r")
void deleteCriterionGroup(#Param("criterionGroupId") Long criterionGroupId);
then I'm trying to get this CriterionGroup by id
criterionGroupRepository.findOne(id);
and it returns NULL. So far so good.
Right after that I'm trying to get group object from Criterion that was in the deleted CriterionGroup and it returns.. deleted CriterionGroup
criterionRepository.findOne(criterion.getId()).getGroup()
What am I doing wrong ? Everything worked fine on SDN 3.4.4.RELEASE and Neo4j 2.3.3 but with Neo4j 3.0.1 SDN 4.1.1.RELEASE due to my limited knowledge I have a lot of unexpected situations.
Also, Is it okay to have a following relationship definition in a one entity(I have removed enforceTargetType )
#Relationship(type = CONTAINS, direction = Relationship.INCOMING)
private Set<Decision> parentDecisions = new HashSet<>();
#Relationship(type = CONTAINS, direction = Relationship.OUTGOING)
private Set<Decision> childDecisions = new HashSet<>();
they have different directions.
As far as I can tell, you're having this CriterionGroup re-appear because you deleted it via a custom query, thus bypassing the OGM. The graph knows of your change, but the OGM's mapping context does not.
The solution is to deregister this entity from the session after you've deleted it via a custom query using Session.detachEntity(id), or, refresh the entire session with session.clear().
Yes, it is okay to have the CONTAINS relationship definitions, please remember that for any relationship marked INCOMING, you must annotate accessors, mutators and properties for parentDecisions if they exist with #Relationship(type = CONTAINS, direction = Relationship.INCOMING)
Update: Also make sure your object model is in sync with what you're deleting via custom query. i.e. if you're deleting the CriterionGroup via a custom query, you should also update your object model to reflect this i.e. criterion.group=null. Or, clear your session completely using session.clear() and reload all dependent entities.
I have a following SDN entity:
NodeEntity
public class Comment extends Commentable {
private final static String CREATED_BY = "CREATED_BY";
private final static String COMMENTED_ON = "COMMENTED_ON";
#RelatedTo(type = COMMENTED_ON, direction = Direction.OUTGOING)
private Commentable commentable;
#RelatedTo(type = CREATED_BY, direction = Direction.OUTGOING)
private User author;
private String text;
....
}
and a following Cypher query:
#Override
#Query("MATCH (c:Comment) WHERE id(c) = {commentId} RETURN c")
Comment findOne(#Param("commentId") Long commentId);
As a result, this query only returns author.id for User author mapping.
How to change this query in order to prepopulate for example author.username also ?
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 7 years ago.
Improve this question
In my Neo4j/Neo4j Spring Data application I have a following entities:
VoteGroup contains relationships VOTED_ON and VOTED_FOR to entities Criterion and Decision and list of Vote
#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";
#GraphId
private Long id;
#RelatedTo(type = VOTED_FOR, direction = Direction.OUTGOING)
private Decision decision;
#RelatedTo(type = VOTED_ON, direction = Direction.OUTGOING)
private Criterion criterion;
#RelatedTo(type = CONTAINS, direction = Direction.OUTGOING)
private Set<Vote> votes = new HashSet<>();
private double avgVotesWeight;
private long totalVotesCount;
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
VoteGroup voteGroup = (VoteGroup) o;
if (id == null)
return super.equals(o);
return id.equals(voteGroup.id);
}
#Override
public int hashCode() {
return id != null ? id.hashCode() : super.hashCode();
}
.....
}
Vote entity looks like:
#NodeEntity
public class Vote extends BaseEntity {
private final static String CONTAINS = "CONTAINS";
private final static String CREATED_BY = "CREATED_BY";
#GraphId
private Long id;
#RelatedTo(type = CONTAINS, direction = Direction.INCOMING)
private VoteGroup group;
#RelatedTo(type = CREATED_BY, direction = Direction.OUTGOING)
private User author;
private double weight;
....
}
public class BaseEntity {
private Date createDate;
private Date updateDate;
public BaseEntity() {
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getUpdateDate() {
return updateDate;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
}
also. I use Neo4j hook based on BaseEntity:
#Configuration
#EnableNeo4jRepositories(basePackages = "com.example")
#EnableTransactionManagement
public class Neo4jConfig extends Neo4jConfiguration implements BeanFactoryAware {
...
/**
* Hook into the application lifecycle and register listeners that perform
* behaviour across types of entities during this life cycle
*
*/
#Bean
protected ApplicationListener<BeforeSaveEvent<BaseEntity>> beforeSaveEventApplicationListener() {
return new ApplicationListener<BeforeSaveEvent<BaseEntity>>() {
#Override
public void onApplicationEvent(BeforeSaveEvent<BaseEntity> event) {
BaseEntity entity = event.getEntity();
if (entity.getCreateDate() == null) {
entity.setCreateDate(new Date());
} else {
entity.setUpdateDate(new Date());
}
}
};
}
...
}
in order to make a vote, I have implemented following method VoteGroupDaoImpl.createVote:
#Service
#Transactional
public class VoteGroupDaoImpl implements VoteGroupDao {
#Autowired
private VoteRepository voteRepository;
#Autowired
private VoteGroupRepository voteGroupRepository;
#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);
}
voteGroup = voteGroupRepository.save(voteGroup);
return voteRepository.save(new Vote(voteGroup, author, weight, description));
}
...
}
and
#Repository
public interface VoteGroupRepository extends GraphRepository<VoteGroup>, RelationshipOperationsRepository<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);
}
Right now, method VoteGroupDaoImpl.createVote works really slow with a huge latency .. what can be a reason of that ?
ADDED PROFILE output
for
MATCH (d:Decision)<-[:VOTED_FOR]-(vg:VoteGroup)-[:VOTED_ON]->(c:Criterion) WHERE id(d) = {decisionId} AND id(c) = {criterionId} RETURN vg
Cypher version: CYPHER 2.2, planner: COST. 33 total db hits in 181 ms.
PROFILE Java code:
Rich profiler information:
HTML page with profiler information
Some ideas that may help:
Execute the query:
MATCH (d:Decision)<-[:VOTED_FOR]-(vg:VoteGroup)-[:VOTED_ON]->(c:Criterion) WHERE id(d) = {decisionId} AND id(c) = {criterionId} RETURN vg
from the web interface or the console and check how it behaves. Try for the same ids you use in the app. Check what is the execution time.
Has VoteGroup numerous relations to Votes? If yes, can you remove:
#RelatedTo(type = CONTAINS, direction = Direction.OUTGOING)
private Set<Vote> votes = new HashSet<>();
and keep information about relation on the Vote side only? Can you check the performance after that change?
Can you use some kind of a profiler tool to identify the exact place of performance problems? Right now it may be still difficult to guess...
Does the code behave as it should? Don you have any duplicates in the DB? Maybe you have bugs in your hashCode/equals methods that cause much more changes in the DB than there really should be?
You could try to reformulate the getVoteGroupForDecisionOnCriterion query as follows, in order to avoid the cartesian product:
MATCH (d:Decision) WHERE id(d) = {decisionId}
WITH d MATCH (c:Criterion) WHERE id(c) = {criterionId}
WITH d,c MATCH d<-[:VOTED_FOR]-(vg:VoteGroup)-[:VOTED_ON]->c
RETURN vg
I'm moved to new Neo4j 2.2.4 and SDN 3.4.0.RC1 and the issue disappeared
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>();