I am using neo4j version 1.8.1 and spring-data-neo4j version 2.2.0. RELEASE.
The problem is about neo4j saving speed. I could not realize why this happens. The node is persisting into db for about 30 second. Here is my model class.
#NodeEntity
public class GraphUser {
public static final String FACEBOOK_FRIEND = "FACEBOOK_FRIEND";
public static final String TWITTER_FOLLOW = "TWITTER_FOLLOW";
public static final String CONTACT = "CONTACT";
public static final String KNOWS = "KNOWS";
public static final String BLOCKED = "BLOCKED";
public static final String FAVORITE = "FAVORITE";
#GraphId
private Long id;
#Indexed(unique = true, indexType = IndexType.FULLTEXT, indexName = "userIdIndex")
private String userId;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "facebookIdIndex")
private String facebookId;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "twitterIdIndex")
private String twitterId;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "emailIndex")
private String email;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "phoneNumberIndex")
private String phoneNumber;
private String knowsLevel;
#RelatedTo(type = FACEBOOK_FRIEND, direction = Direction.BOTH)
#Fetch
private Set<GraphUser> facebookRelations = new HashSet<GraphUser>();
#RelatedTo(type = TWITTER_FOLLOW, direction = Direction.OUTGOING)
#Fetch
private Set<GraphUser> twitterRelations = new HashSet<GraphUser>();
#RelatedTo(type = CONTACT, direction = Direction.OUTGOING)
#Fetch
private Set<GraphUser> contacts = new HashSet<GraphUser>();
#RelatedTo(type = KNOWS, direction = Direction.OUTGOING)
#Fetch
private Set<GraphUser> knows = new HashSet<GraphUser>();
#RelatedTo(type = BLOCKED, direction = Direction.OUTGOING)
#Fetch
private Set<GraphUser> blocks = new HashSet<GraphUser>();
#RelatedTo(type = FAVORITE, direction = Direction.OUTGOING)
#Fetch
private Set<GraphUser> favorites = new HashSet<GraphUser>();
#Query(value = "start user=node({self}), user2=node(*), matchedUser=node(*) " +
"where has(user2.userId) and has(matchedUser.userId) and has(user.knowsLevel) and has(user2.knowsLevel) and has(matchedUser.knowsLevel) " +
"and " +
"user.userId<>matchedUser.userId " +
"and " +
"(" +
"(user.knowsLevel='ALL' and (matchedUser.knowsLevel='ALL' or (user)<-[:KNOWS]-(matchedUser) or ((user)<-[:KNOWS]-(user2)<-[:KNOWS]-(matchedUser) and matchedUser.knowsLevel='SECOND'))) " +
"or " +
"(user.knowsLevel='SECOND' and ((user)-[:KNOWS]->(matchedUser) or (user)-[:KNOWS]->(user2)-[:KNOWS]->(matchedUser)) and (matchedUser.knowsLevel='ALL' or (user)<-[:KNOWS]-(matchedUser) or ((user)<-[:KNOWS]-(user2)<-[:KNOWS]-(matchedUser) and matchedUser.knowsLevel='SECOND'))) " +
"or " +
"(user.knowsLevel='FIRST' and (user)-[:KNOWS]->(matchedUser) and (matchedUser.knowsLevel='ALL' or (user)<-[:KNOWS]-(matchedUser) or ((user)<-[:KNOWS]-(user2)<-[:KNOWS]-(matchedUser) and matchedUser.knowsLevel='SECOND')))" +
") " +
"return matchedUser")
#Fetch
private Iterable<GraphUser> matchedUsers;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getFacebookId() {
return facebookId;
}
public void setFacebookId(String facebookId) {
this.facebookId = facebookId;
}
public String getTwitterId() {
return twitterId;
}
public void setTwitterId(String twitterId) {
this.twitterId = twitterId;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getKnowsLevel() {
return knowsLevel;
}
public void setKnowsLevel(String knowsLevel) {
this.knowsLevel = knowsLevel;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Set<GraphUser> getFacebookRelations() {
return facebookRelations;
}
public void setFacebookRelations(Set<GraphUser> facebookRelations) {
this.facebookRelations = facebookRelations;
}
public Set<GraphUser> getTwitterRelations() {
return twitterRelations;
}
public void setTwitterRelations(Set<GraphUser> twitterRelations) {
this.twitterRelations = twitterRelations;
}
public Set<GraphUser> getContacts() {
return contacts;
}
public void setContacts(Set<GraphUser> contacts) {
this.contacts = contacts;
}
public Set<GraphUser> getKnows() {
return knows;
}
public void setKnows(Set<GraphUser> knows) {
this.knows = knows;
}
public Set<GraphUser> getBlocks() {
return blocks;
}
public void setBlocks(Set<GraphUser> blocks) {
this.blocks = blocks;
}
public Set<GraphUser> getFavorites() {
return favorites;
}
public void setFavorites(Set<GraphUser> favorites) {
this.favorites = favorites;
}
}
What may I be missing ?
Thank you.
Two suspects
Indexes: I see you have around 5 indexes. Write takes longer when you have more indexes as the write should also update the index as part of the transaction.
Eager Fetch: I see you have around 6 relationships that you annotated with #Fetch. I believe spring-data-neo4j tries to re-fetch everything after the commit. And if these relationships/nodes have #Fetch for their own properties annotated with #Fetch then they are recursively fetched.
I suggest you start testing this by first removing #Fetch and then #Index and see if that improves performance. Are you also doing batch inserts?
Related
I am trying to get the following to be inserted into firebase but read that we can only store one information at a time? I thought that we can somehow store multiple information under a single unique id? Below is the code and I am trying to insert information such as phone numbers, address etc. With the address, can we use a string for the whole address or must we break it down to integer plus string? Also, should i be using "long" for phone number? I am also not sure if I should be using multiple Firebasedatabase for this?
public class MainActivity extends AppCompatActivity {
private Button logout;
private EditText edit;
private EditText number;
private EditText address;
private EditText phone;
private EditText postcode;
private Button add;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
logout = findViewById(R.id.logout);
edit = findViewById(R.id.edit);
add = findViewById(R.id.add);
number = findViewById(R.id.number);
address = findViewById(R.id.address);
phone = findViewById(R.id.phone);
postcode = findViewById(R.id.postcode);
logout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FirebaseAuth.getInstance().signOut();
Toast.makeText(MainActivity.this, "Logged Out", Toast.LENGTH_SHORT).show();
startActivity(new Intent(MainActivity.this, StartActivity.class));
finish();
}
});
add.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String txt_name = edit.getText().toString();
// int txtnumber = Integer.valueof(number);
String txt_address = address.getText().toString();
// Long phone_number = Long.parseLong(phone.getText().toString().trim());
// Long postcode2 = Long.parseLong(postcode.getText().toString().trim());
if (txt_name.isEmpty()) {
Toast.makeText(MainActivity.this, "No name entered!", Toast.LENGTH_SHORT).show();
} else {
FirebaseDatabase.getInstance("https://medical-review-in-australia.firebaseio.com/").getReference().child("Medical Clinic").push().child("Name").setValue(txt_name, txt_address);
// FirebaseDatabase.getInstance("https://medical-review-in-australia.firebaseio.com/").getReference().child("Medical Clinic").setValue(txt_address);
// FirebaseDatabase.getInstance("https://medical-review-in-australia.firebaseio.com/").getReference().child("Medical Clinic").child("Name:").child("Address No:").child("Address Name:").setValue(txt_address);
// FirebaseDatabase.getInstance("https://medical-review-in-australia.firebaseio.com/").getReference().child("Medical Clinic").child("Name:").child("Address No:").child("Address Name").child("Phone number:").setValue(phone_number);
// FirebaseDatabase.getInstance("https://medical-review-in-australia.firebaseio.com/").getReference().child("Medical Clinic").child("Name:").child("Address No:").child("Address Name").child("Phone number:").child("Postcode:").setValue(postcode2);
}
}
});
}
}
Create a class named Info
public class Info {
private String name;
private String address;
private String phoneNo;
private String postcode;
public Info(String name, String address, String phoneNo, String postcode) {
this.name = name;
this.address = address;
this.phoneNo = phoneNo;
this.postcode = postcode;
}
public Info() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhoneNo() {
return phoneNo;
}
public void setPhoneNo(String phoneNo) {
this.phoneNo = phoneNo;
}
public String getPostcode() {
return postcode;
}
public void setPostcode(String postcode) {
this.postcode = postcode;
}
}
then in your activity
add.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String txt_name = edit.getText().toString();
// int txtnumber = Integer.valueof(number);
String txt_address = address.getText().toString();
// Long phone_number = Long.parseLong(phone.getText().toString().trim());
// Long postcode2 = Long.parseLong(postcode.getText().toString().trim());
if (txt_name.isEmpty()) {
Toast.makeText(MainActivity.this, "No name entered!", Toast.LENGTH_SHORT).show();
} else {
String key = FirebaseDatabase.getInstance("https://medical-review-in-australia.firebaseio.com/").getReference().child("Medical Clinic").push().getKey();
Info info = new Info(name,String.valueOf(txtnumber),String.valueOf(phone_number),String.valueOf(postcode2));
FirebaseDatabase.getInstance("https://medical-review-in-australia.firebaseio.com/").getReference().child(key).setValue(info);
});
We are using Spring Boot 2.2.5 and Spring Data Neo4J. We have nodes with relationships which we have mapped with Spring NodeEntity and RelationshipEntity. What we are noticing is if there are nodes with lots of first level relationships (e.g. over 1500), its taking time (over 1 second) to get and update the entity/relationships.
Are there any best practices on how to improve performance for the relationships. Are there ways we can use pagination, limits etc.?
Code:
#NodeEntity
public class Node {
#Id
#GeneratedValue
private Long id;
private String name;
#Relationship(type = RelatedNode.TYPE, direction = Relationship.UNDIRECTED)
private Set<RelatedNode> relatedNodes = new HashSet<>();
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 Set<RelatedNode> getRelatedNodes() {
return relatedNodes;
}
public void setRelatedNodes(Set<RelatedNode> relatedNodes) {
this.relatedNodes = relatedNodes;
}
public RelatedNode addRelatedNode(Node relatedNode, long count) {
final RelatedNode node = this.relatedNodes.stream()
.filter(i -> (relatedNode.getId().equals(i.getEnd().getId())) || (relatedNode.getId().equals(i.getStart().getId())))
.findFirst()
.orElseGet(() -> {
RelatedNode newRelatedNode = new RelatedNode();
newRelatedNode.setStart(this);
newRelatedNode.setEnd(relatedNode);
newRelatedNode.setCount(count);
this.relatedNodes.add(newRelatedNode);
return newRelatedNode;
});
return node;
}
public RelatedNode updateRelatedNode(Node relatedNode, long count) {
final RelatedNode node = this.relatedNodes.stream()
.filter(i -> (relatedNode.getId().equals(i.getEnd().getId())) || (relatedNode.getId().equals(i.getStart().getId())))
.findFirst().get();
if (node != null) {
node.setCount(count);
}
return node;
}
public void deleteRelatedNode(Node relatedNode) {
final RelatedNode node = this.relatedNodes.stream()
.filter(i -> (relatedNode.getId().equals(i.getEnd().getId())) || (relatedNode.getId().equals(i.getStart().getId())))
.findFirst().get();
this.relatedNodes.remove(node);
}
}
#RelationshipEntity(type = RelatedNode.TYPE)
public class RelatedNode {
public static final String TYPE = "RELATED_TO";
#Id
#GeneratedValue
private Long id;
#StartNode
private Node start;
#EndNode
private Node end;
private long count;
public Long getId() {
return id;
}
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
public Node getEnd() {
return end;
}
public void setEnd(Node end) {
this.end = end;
}
public Node getStart() {
return start;
}
public void setStart(Node start) {
this.start = start;
}
public void setId(Long id) {
this.id = id;
}
}
Notes:
I am having to add/update related nodes by using addRelatedNode/updateRelatedNode methods. If I create/update relationships without these methods, the relationships are duplicated as the relationship entity has attributes.
I have one question. I am using neo4j-ogm snapshot 1.5 .
I have the following classes:
#NodeEntity
public abstract class Entity {
#GraphId
protected Long id;
#Expose
protected String code = null;
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || id == null || getClass() != o.getClass())
return false;
Entity entity = (Entity) o;
if (!id.equals(entity.id))
return false;
return true;
}
#Override
public int hashCode() {
return (id == null) ? -1 : id.hashCode();
}
public Long getId(){
return id;
}
public void setId(Long neo4jId){
this.id = neo4jId;
}
public String getCode(){
return code;
}
public void setCode(String code){
this.code = code;
}
}
public class PropertyGroup extends Entity{
#Expose
private String name;
public PropertyGroup(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class User extends Entity {
private Long registration_date;
private Long last_login_date;
private Boolean is_admin = false;
private String push_dev;
private String push_id;
private Boolean push_enabled = false;
#Expose
private String avatar;
#Expose
private String avatarUrl;
#Expose
private String name;
#Expose
private volatile String password;
#Expose
private int likes = 0;
#Expose
private int questionCount = 0;
#Expose
private int followersCount = 0;
#Expose
private boolean isFollowing = false;
// public Set<UserPropertyRelation> properties;
// #Relationship(type = ModelRelType.ANSWERED)
// public Set<UserAnsweredRelation> userAnswers;
//
// #Relationship(type = ModelRelType.LIKES)
// private Set<LikesQuestionRelation> questionsLiked;
#Expose
#Relationship(type = ModelRelType.HAS_PROPERTY)
private Set<PropertyGroup> properties;
// private Profile userProfile;
// private List<Fact> facts;
// #Expose
// #Relationship(type = ModelRelType.OWNS)
// private List<Question> questions;
public User(){
// this.properties = new LinkedHashSet<UserPropertyRelation>();
// this.userAnswers = new LinkedHashSet<UserAnsweredRelation>();
// this.userProperties = new HashSet<PropertyGroup>();
// this.setFacts(new ArrayList<Fact>());
this.properties = new HashSet<PropertyGroup>();
}
public User(long regDate, long lastLoginDate, boolean isAdmin,
String pushDev, String pushId, boolean pushEnabled){
this();
this.registration_date = regDate;
this.last_login_date = lastLoginDate;
this.is_admin = isAdmin;
this.push_dev = pushDev;
this.push_id = pushId;
this.push_enabled = pushEnabled;
}
// public void addUserAnsweredRelation(UserAnsweredRelation answer){
// answer.setStartNode(this);
// this.userAnswers.add(answer);
// }
//
// public Set<UserAnsweredRelation> getUserAnsweredRelations() {
// return this.userAnswers;
// }
// public void setUserAnsweredRelations(Set<UserAnsweredRelation> userAnswers){
// for(UserAnsweredRelation a : userAnswers){
// a.setStartNode(this);
// }
//
// this.userAnswers = userAnswers;
// }
//
// public void addUserPropertyRelation(UserPropertyRelation rel){
// rel.setUser(this);
// properties.add(rel);
// }
//
// public void setUserPropertyRelations(Set<UserPropertyRelation> properties){
// for(UserPropertyRelation r: properties){
// r.setUser(this);
// }
//
// this.properties = properties;
// }
// public Set<UserPropertyRelation> getUserPropertyRelations(){
// return this.properties;
// }
public long getRegistrationDate() {
return registration_date;
}
public void setRegistrationDate(long registrationDate) {
this.registration_date = registrationDate;
}
public long getLastLoginDate() {
return last_login_date;
}
public void setLastLoginDate(long lastLoginDate) {
this.last_login_date = lastLoginDate;
}
public boolean isAdmin() {
return is_admin;
}
public void setAdmin(boolean isAdmin) {
this.is_admin = isAdmin;
}
public String getPushDev() {
return push_dev;
}
public void setPushDev(String pushDev) {
this.push_dev = pushDev;
}
public String getPushId() {
return push_id;
}
public void setPushId(String pushId) {
this.push_id = pushId;
}
public boolean isPushEnabled() {
return push_enabled;
}
public void setPushEnabled(boolean pushEnabled) {
this.push_enabled = pushEnabled;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<PropertyGroup> getProperties() {
return properties;
}
public void setProperties(Set<PropertyGroup> properties) {
this.properties = properties;
}
// public Profile getUserProfile() {
// return userProfile;
// }
//
// public void setUserProfile(Profile userProfile) {
// this.userProfile = userProfile;
// }
// public Set<LikesQuestionRelation> getQuestionsLiked() {
// return questionsLiked;
// }
//
// public void setQuestionsLiked(Set<LikesQuestionRelation> likes) {
// this.questionsLiked = likes;
// }
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// public List<Fact> getFacts() {
// return facts;
// }
//
// public void setFacts(List<Fact> facts) {
// this.facts = facts;
// }
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public int getLikes() {
return likes;
}
public void setLikes(int likes) {
this.likes = likes;
}
public int getQuestionCount() {
return questionCount;
}
public void setQuestionCount(int questionCount) {
this.questionCount = questionCount;
}
public int getFollowersCount() {
return followersCount;
}
public void setFollowersCount(int followersCount) {
this.followersCount = followersCount;
}
public boolean isFollowing() {
return isFollowing;
}
public void setFollowing(boolean isFollowing) {
this.isFollowing = isFollowing;
}
// public List<Question> getQuestions() {
// return questions;
// }
//
// public void setQuestions(List<Question> questions) {
// this.questions = questions;
// }
When I am trying to do the following:
SessionFactory sessionFactory = new SessionFactory(modelsPackageName);
Session session = sessionFactory.openSession(url);
String cypher = "MATCH (u:User {code: {CODE}})-[h:HAS_PROPERTY]->(pg:PropertyGroup) " +
"RETURN u, h, pg";
Map<String, Object> params = new HashMap<String, Object>();
params.put("CODE", "fc48b19ba6f8427a03d6e5990bcef99a28f55592b80fe38731cf805ed188cabf");
// System.out.println(Util.mergeParamsWithCypher(cypher, params));
User u = session.queryForObject(User.class, cypher, params);
The user Object (u) never contains any properties (PropertyGroup entity is not mapped).
What am I doing wrong?
Any help would be appreciated.
Regards,
Alex
If you're using queryForObject return just one column- the object, in your case u.
Neo4j OGM 1.x does not support mapping of custom query results to domain entities, so you will have to return the entity ID, and then do an additional load-by-id specifying a custom depth.
OGM 2.0 (currently 2.0.0-M01) does support mapping custom query results to entities. Your query will remain the same (i.e. return u,h,pg) but instead you'll use the query() method that returns a Result. From the result, you'll be able to get your User entity by column-name u and it'll be hydrated with the PropertyGroups it is related to.
Update:
The dependencies for OGM 2.0.0-M01 are
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-api</artifactId>
<version>2.0.0-M01</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-core</artifactId>
<version>2.0.0-M01</version>
</dependency>
Be sure to read the about the configuration changes since you're upgrading from OGM 1.x http://neo4j.com/docs/ogm/java/2.0.0-M01/#reference_setup
A summary of new features: http://neo4j.com/blog/neo4j-ogm-2-0-milestone-1/
I have a weird problem with the node auto fetching in Spring Data Neo4j 4.0.0.
I have a class like below :
#NodeEntity
public class FilterVersionChange extends UnitVersion {
#GraphId
private Long id;
public FilterVersionChange() {
super();
}
public FilterVersionChange(String description, Long creationDate)
{
super(description, creationDate);
}
#Relationship(type="CONTAINS", direction = Relationship.OUTGOING)
private Set<FilterState> filterStates;
#Relationship(type="REFERENCES", direction = Relationship.OUTGOING)
private FilterVersionChange referencedFilterVersionChange;
#Relationship(type="ADDED", direction = Relationship.OUTGOING)
private Set<FilterState> newFilterStates;
#Relationship(type="DELETED", direction = Relationship.OUTGOING)
private Set<FilterState> deletedFilterStates;
#Relationship(type="MODIFIED", direction = Relationship.OUTGOING)
private Set<ModifiedUnitState> modifiedFilterStates;
#Relationship(type="TRACKS", direction = Relationship.INCOMING)
private FilterVersion filterVersion;
#Relationship(type = "CREATED_ON", direction = Relationship.OUTGOING)
private TimeTreeSecond timeTreeSecond;
public void createdOn(TimeTreeSecond timeTreeSecond) {
this.timeTreeSecond = timeTreeSecond;
}
public void contains(Set<FilterState> filterStates) {
this.filterStates = filterStates;
}
public void references(FilterVersionChange referencedFilterVersionChange) {
this.referencedFilterVersionChange = referencedFilterVersionChange;
}
public void added(Set<FilterState> newFilterStates) {
this.newFilterStates = newFilterStates;
}
public void deleted(Set<FilterState> deletedFilterStates) {
this.deletedFilterStates = deletedFilterStates;
}
public void modified(Set<ModifiedUnitState> modifiedFilterStates) {
this.modifiedFilterStates = modifiedFilterStates;
}
public void trackedIn(FilterVersion filterVersion) {
this.filterVersion = filterVersion;
}
public FilterVersion getFilterVersion() {
return filterVersion;
}
public Set<FilterState> getFilterStates() {
return filterStates;
}
}
In the database, I have one FilterVersionChange node with several FilterStates nodes attached to it via 'CONTAINS' and 'ADDED' relationships. Assume that I have the id of that FilterVersionChange node and I want to get the node by calling findOne(id). But, what I get from it is null value for the filterStates variable.
As I understand from the documentation, findOne should retrieve the depth of 1 by default. But I really have no idea why I get the value of null with the filterStates variable.
Thank you in advance and your suggestion would be really appreciated!
EDIT
This is the function where the insertion code is.
public FilterVersionChange createNewFilterVersionChange(String projectName,
String filterVersionName,
String filterVersionChangeDescription,
Set<FilterState> filterStates)
{
Long filterVersionNodeId = filterVersionRepository.findFilterVersionByName(projectName, filterVersionName);
if(filterVersionNodeId != null)
{
FilterVersion newFilterVersion = filterVersionRepository.findOne(filterVersionNodeId, 2);
HashMap<String, Filter> filterHashMap = new HashMap<String, Filter>();
Iterable<Filter> filters = filterRepository.findAll();
if(filters.iterator().hasNext())
{
for(Filter filter : filters)
{
filterHashMap.put(filter.getMatchingString(), filter);
}
}
for(FilterState filterState : filterStates)
{
Filter filter;
if(filterHashMap.isEmpty() == false)
{
filter = filterHashMap.get(filterState.getMatchingString());
}
else
{
filter = new Filter(filterState.getMatchingString(), filterState.getMatchingType());
filter.belongsTo(newFilterVersion.getProject());
}
filterState.stateOf(filter);
}
Date now = new Date();
FilterVersionChange filterVersionChange = new FilterVersionChange();
filterVersionChange.setDescription(filterVersionChangeDescription);
filterVersionChange.setCreationDate(now.getTime());
filterVersionChange.contains(filterStates);
filterVersionChange.added(filterStates);
filterVersionChange.trackedIn(newFilterVersion);
TimeTreeSecond timeInstantNode = timeTreeService.getFilterTimeInstantNode(projectName, now.getTime());
filterVersionChange.createdOn(timeInstantNode);
FilterVersionChange addedFilterVersionChange = filterVersionChangeRepository.save(filterVersionChange);
return addedFilterVersionChange;
}
else
{
return null;
}
}
Here is the FilterState class
#NodeEntity
public class FilterState {
#GraphId
private Long id;
private String matchingString;
private String matchingType;
public FilterState() {
}
public FilterState(String matchingString, String matchingType) {
this.matchingString = matchingString;
setMatchingType(matchingType);
}
#Relationship(type="STATE_OF", direction = Relationship.OUTGOING)
private Filter filter;
#Relationship(type="PART_OF", direction = Relationship.OUTGOING)
private CodeUnit codeUnit;
#Relationship(type="CONTAINS", direction = Relationship.INCOMING)
private FilterVersionChange containedInFilterVersionChange;
#Relationship(type="ADDED", direction = Relationship.INCOMING)
private FilterVersionChange addedInFilterVersionChange;
public void setMatchingString(String matchingString) {
this.matchingString = matchingString;
}
public void setMatchingType(String matchingType) {
String type = null;
if(matchingType.equalsIgnoreCase("RegexFilter"))
{
type = "RegexFilter";
}
else if(matchingType.equalsIgnoreCase("ClassFilter"))
{
type = "ClassFilter";
}
this.matchingType = type;
}
public void stateOf(Filter filter) {
this.filter = filter;
}
public void partOf(CodeUnit codeUnit) {
this.codeUnit = codeUnit;
}
public String getMatchingString() {
return matchingString;
}
public String getMatchingType() {
return matchingType;
}
public Filter getFilter() {
return filter;
}
public Long getId() {
return id;
}
public void addedIn(FilterVersionChange addedInFilterVersionChange) {
this.addedInFilterVersionChange = addedInFilterVersionChange;
}
public FilterVersionChange getContainedInFilterVersionChange() {
return containedInFilterVersionChange;
}
}
and here is the gradle file
def RMQVersion = "3.3.4"
def GSONVersion = "2.3.1"
def Neo4jVersion = "2.2.1"
def Neo4jTimeTreeVersion = "2.2.1.30.21"
def SpringVersion = "4.1.6.RELEASE"
def SDNVersion = "4.0.0.BUILD-SNAPSHOT"
def JunitVersion = "4.12"
apply plugin: 'java'
apply plugin: 'maven'
sourceCompatibility = 1.5
version = '1.0'
repositories {
mavenCentral()
maven {
url("http://maven.springframework.org/milestone")
}
maven {
url("http://repo.spring.io/libs-snapshot")
}
}
dependencies {
compile "org.springframework.data:spring-data-neo4j:${SDNVersion}"
compile "org.neo4j:neo4j:${Neo4jVersion}"
compile "com.graphaware.neo4j:timetree:${Neo4jTimeTreeVersion}"
compile "com.rabbitmq:amqp-client:${RMQVersion}"
compile "com.google.code.gson:gson:${GSONVersion}"
testCompile group: 'org.springframework.data', name: 'spring-data-neo4j', version: SDNVersion, classifier: 'tests'
testCompile group: 'org.springframework', name: 'spring-test', version: SpringVersion
testCompile group: 'junit', name: 'junit', version: JunitVersion
}
This issue has been fixed post 4.0.0M1, please use the latest build snapshot, thanks.
I use this code:
JSF:
<p:treeTable id="treeSkill" value="#{skillManager.rootSkill}"
var="skill" selectionMode="single" widgetVar="skillsTreeTable"
style="border: 0;">
<p:ajax event="expand"
listener="#{skillManager.expandNodeListener}" />
<p:column> ..... </p:column>
<p/treeTable>
SkillManager:
#Named
#SessionScoped
public class SkillManager implements Serializable {
private static final long serialVersionUID = 1L;
private TreeNode rootSkill;
public SkillManager() {
initSkillTree();
}
public void expandNodeListener(NodeExpandEvent nee) {
TreeNode treeNode = nee.getTreeNode();
if (treeNode instanceof FetchChildren)
((FetchChildren) treeNode).fetchChildren();
if (treeNode instanceof LazySkillTreeNode)
((LazySkillTreeNode) treeNode).fetchSubchildren();
}
private void initSkillTree() {
rootSkill = new DefaultTreeNode("Root", null);
Skill realRootSkill = HrDaoFactory.getInstance().getSkillDAO().getRootSkill();
TreeNode realRootNode = new LazySkillTreeNode(realRootSkill, rootSkill);
for (Skill skill : realRootSkill.getChildrensSkills()) {
LazySkillTreeNode node = new LazySkillTreeNode(skill, realRootNode);
node.fetchChildren();
}
RequestContext.getCurrentInstance().update("woCatalogTabView:skillTreeForm");
}
}
LazySkillTreeNode:
public class LazySkillTreeNode extends LazyTreeNode implements FetchChildren {
private static final long serialVersionUID = 8856168173751148652L;
private boolean childrenFetched;
public LazySkillTreeNode(Object data, TreeNode parent) {
super(data, parent);
}
#Override
public void fetchChildren() {
if (childrenFetched)
return;
for (Skill skill : ((Skill) super.getData()).getChildrensSkills())
new LazySkillTreeNode(skill, this);
childrenFetched = true;
}
}
LazyTreeNode:
public abstract class LazyTreeNode extends DefaultTreeNode {
private static final long serialVersionUID = 8839307424434170537L;
private boolean subChildrenFetched;
public LazyTreeNode(Object data, TreeNode parent) {
super(data, parent);
}
public void fetchSubchildren() {
if (subChildrenFetched || isLeaf())
return;
List<TreeNode> treeNodeList = getChildren();
for (TreeNode node : treeNodeList) {
if (node instanceof FetchChildren)
((FetchChildren) node).fetchChildren();
}
subChildrenFetched = true;
}
}
Everything works fine, but if add/delete elements (after all this operations we call method initSkillTree() for rebuild tree) a lot of times, or if 2 or more users start to do it, we beginning to recieve in response from server this string:
<?xml version='1.0' encoding='UTF-8'?>
<partial-response><error><error-name>class java.lang.StackOverflowError</error-name><error-message><![CDATA[]]></error-message></error></partial-response>
Other problem that i don't have any information about error. No information in log files. In server.log nothing to.
We use: JSF (Mojarra 2.14), Primefaces 3.41, JBOSS 7.
And in the end error was in Controller class where method:
public void addOrUpdateSkill(Skill skill) {
Session session = null;
try {
session = HibernateUtil.getCurrentSession();
session.beginTransaction();
session.saveOrUpdate(skill);
session.getTransaction().commit();
evictAllSkillsFromSession();
} catch (Throwable e) {
logger.fatal(skill, e);
if (session.getTransaction() != null && session.getTransaction().isActive())
session.getTransaction().rollback();
throw new RuntimeException(e);
}
}
and stack trace was appeared in the row "logger.fatal(skill, e);"
you must pass the error message by first argument instead of Entity object.
Error appear because of it's toString() method implementation of Skill class:
#Entity
#Table(name = "SKILLS", schema = AppData.HR_SCHEMA)
public class Skill implements Serializable {
private static final long serialVersionUID = -2728239519286686549L;
#Id
#SequenceGenerator(name = "SKILLS_ID_GENERATOR", sequenceName = AppData.HR_SCHEMA + ".SKILLS_ID_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SKILLS_ID_GENERATOR")
private BigDecimal id;
#Column(name = "NAME_ENG")
private String nameEng;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "UPDATED_AT")
private Date updatedAt;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "UPDATED_BY", referencedColumnName = "USER_ID")
private User updatedBy;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
private Skill parentSkill;
#OneToMany(mappedBy = "parentSkill", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Skill> childrensSkills;
#Column(name = "DESCRIPTION")
private String description;
#OneToMany(orphanRemoval = true, mappedBy = "skill")
private List<SkillJoinedAction> skillJoinedActions;
#OneToMany(orphanRemoval = true, mappedBy = "skill")
private List<SkillJoinedEmployee> skillJoinedEmployees;
public Skill() {
}
public Skill(String nameEng, User updateBy, String description) {
this.nameEng = nameEng;
this.updatedBy = updateBy;
this.updatedAt = new Date();
this.setDescription(description);
}
public BigDecimal getId() {
return id;
}
public void setId(BigDecimal id) {
this.id = id;
}
public String getNameEng() {
return this.nameEng;
}
public void setNameEng(String nameEng) {
this.nameEng = nameEng;
}
public Date getUpdatedAt() {
return this.updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
public User getUpdatedBy() {
return updatedBy;
}
public void setUpdatedBy(User updatedBy) {
this.updatedBy = updatedBy;
}
public List<Skill> getChildrensSkills() {
return childrensSkills;
}
public void setChildrensSkills(List<Skill> childrensSkills) {
this.childrensSkills = childrensSkills;
}
public Skill getParentSkill() {
return parentSkill;
}
public void setParentSkill(Skill parentSkill) {
this.parentSkill = parentSkill;
}
#Override
public String toString() {
return "Skill [id=" + id + ", nameEng=" + nameEng + ", updatedAt=" + updatedAt + ", updatedBy=" + updatedBy + ", parentSkill="
+ parentSkill + ", childrensSkills=" + childrensSkills + "]";
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<SkillJoinedAction> getSkillJoinedActions() {
return skillJoinedActions;
}
public void setSkillJoinedActions(List<SkillJoinedAction> skillJoinedActions) {
this.skillJoinedActions = skillJoinedActions;
}
public List<SkillJoinedEmployee> getSkillJoinedEmployees() {
return skillJoinedEmployees;
}
public void setSkillJoinedEmployees(List<SkillJoinedEmployee> skillJoinedEmployees) {
this.skillJoinedEmployees = skillJoinedEmployees;
}
}
as you can see in method:
#Override
public String toString() {
return "Skill [id=" + id + ", nameEng=" + nameEng + ", updatedAt=" + updatedAt + ", updatedBy=" + updatedBy + ", parentSkill="
+ parentSkill + ", childrensSkills=" + childrensSkills + "]";
}
was called method toString() on parentSkill who in his turn call toString() on childrensSkills... infinite recursion.