Neo4j-ogm "Filter" to search an parent entity by some of parent property and its child property - neo4j

#NodeEntity
public class User {
private Long id,
private String email,
#Relationship(type = "hasOne", direction = Relationship.OUTGOING)
private Profile profile
}
#NodeEntity
public class Profile {
private Long id;
private String firstName
}
I just need to load user objects where email = "abc" and firstName="xyz".
I am using spring-data-neo4j 4.2.3.RELEASE.
How effectively use ogm filter on this query (I dont need native query)?

You can use the nested Filters as following:
Filter emailFilter = new Filter("email", ComparisonOperator.EQUALS, "krishna#abc.in");
Filter firstNameFilter = new Filter("firstName", ComparisonOperator.EQUALS, "krishna");
firstNameFilter.setNestedPath({
new Filter.NestedPathSegment("profile", Profile.class);
});
return session.loadAll(User.class, emailFilter.and(firstNameFilter));

You can use Spring Data derived finders.
In your UserRepository create a method like this:
User findByEmailAndProfileFirstName(String email, String firstName);
Note that you can use only 1 level of nesting in derived finders.

Related

Neo4j spring - Create a 2 way relationship between same type of entities

Type of relation we want is as follows
CREATE (jack:Person {name:"Jack"})-[:KNOWS]->(jill:Person {name:"Jill"})
WITH jack, jill
CREATE (jack)<-[:KNOWS]-(jill);
How do we model this in the following?
#Node
public class User {
#Id
#GeneratedValue
private UUID id;
private String name;
private String phoneNumber;
private String email;
private boolean isActive;
#JsonIgnore
#Relationship(type = "Knows", direction = Relationship.Direction.OUTGOING)
private Set<Knows> knows;
#JsonIgnore
#Relationship(type = "Knows", direction = Relationship.Direction.INCOMING)
private Set<Knows> knownBy;
public User() {
}
public void knows(Knows to) {
if (knows == null) {
knows = new HashSet<>();
}
knows.add(to);
}
public void knownBy(Knows from) {
if (knownBy == null) {
knownBy = new HashSet<>();
}
knownBy.add(from);
}
}
#RelationshipProperties
public class Knows {
private String as;
#CreatedDate
private Instant createdAt;
#JsonIgnore
#TargetNode
private User User;
}
snippet to save the users
User user1 = userRepository.findById(id1);
User user2 = userRepository.findById(id2);
Knows knows = new Knows("as", Instant.now(), user1);
Knows knownBy = new Knows("as", Instant.now(), user2);
user2.knows(knows);
user1.knownBy(knownBy);
userRepository.save(user2);
With this, we are getting StackOverflow while saving the User entity.
We want to understand is this the correct way to model it or is there a better way?
First things first: There is a bug in SDN when it comes to map bidirectional relationships to the database.
Ticket to track is https://jira.spring.io/browse/DATAGRAPH-1469
It will be very certain in the next release.
Giving you scenario I would suggest cleaning up the relationships and only create the OUTGOING relationships like
#Node
public class User {
// ... id etc.
#Relationship("KNOWS")
private List<Knows> knows;
}
When it comes to setting the value, both sides need to get populated:
user1.knows(user2);
user2.knows(user1);
Given the fact that the bug is present until SDN 6.0.3 gets released, I would suggest to either work either unidirected, only connect the Users in one direction or link the Users directly.
As a consequence you would have no access to the properties on the relationship.
#Node
public class User {
// ... id etc.
#Relationship("KNOWS")
private List<User> knows;
}

Prevent Multiple Relationships between two nodes in Neo4J

I am building an application using Spring-Data-Neo4J (4.0.0.RELEASE). The application has two entities, a Person entity, and a Group entity. There is also a relationship entity, Member_Of, which says that a person is a member of a group. The entities look like below
#NodeEntity(label="Person")
public class Person {
protected String uuid;
protected String fullName;
protected String email;
#Relationship(type = RelationshipNames.MEMBER_OF, direction = Relationship.OUTGOING)
protected List<GroupMembership> groupMemberships = new ArrayList<GroupMembership>() ;
}
#NodeEntity(label="Group")
public class Group implements Serializable{
protected static final long serialVersionUID = 1L;
#GraphId
protected Long id;
protected String uuid;
protected String name;
#Relationship(type = RelationshipNames.MEMBER_OF, direction = Relationship.INCOMING)
protected List<GroupMembership> groupMemberships = new ArrayList<GroupMembership>() ;
}
#RelationshipEntity(type = RelationshipNames.MEMBER_OF)
public class GroupMembership implements Serializable{
private static final long serialVersionUID = 1L;
#GraphId Long id;
#Property String uuid;
#StartNode Person member;
#EndNode Group group;
#DateLong
Date date;
}
There is a method which adds members to a group. The method is annotated with #Transactional. The sample code is given below.
#Override
#Transactional(propagation=Propagation.REQUIRED)
public ResponseEntity<GroupVO> addMembersToGroup(String groupUuid, String personUuid, List<String> personsToAdd){
Group group = groupRepository.findGroupByUuid(groupUuid);
List<Person> personsToAdd = IterableUtils.toList(personRepository.findPersonsByUuid(personUuids));
personsToAdd.forEach(personToAdd -> {
GroupMembership existingGroupMembership = getActiveMembership(group.getUuid(), personToAdd.getUuid());
if(existingGroupMembership==null){
GroupMembership newGroupMembership = new GroupMembership(personToAdd, group, GroupRoleNames.MEMBER, member.getUuid(), new Date());
personToAdd.getGroupMemberships().add(newGroupMembership);
group.getGroupMemberships().add(newGroupMembership);
}
groupRepository.save(group);
}
What it tries to do is that, it searches for a existing relationship between the personToAdd and the group. If it returns null, that is, no relationship exists, it adds it to the group.
The problem is sometimes the same person is added multiple times to the same group. This is happenning when two people are running the application and both of them tries to add the same person to the same group.
How do I prevent this from happenning? I need to have a single relationship between a person and the group and not multiple ones.
There will be only one relationship created between two given entities provided all properties on the relationship entity are equal. You have a timestamp which is the culprit here- SDN realises that the two relationships differ because they have different values for a property and goes ahead and creates the second one.
At the moment, SDN does not have configuration to allow you to specify a merge vs create for relationship entities.
You'll probably have to manage some synchronization at the application level.
I had the same issue, and was able to "solve" it by using a custom cypher query. Given that you have added successfully Person and Group entity you can run the following query:
#Query("MATCH (group:Group) " +
"MATCH (person:Person) " +
"WHERE person.uuid={0} AND group.uuid={1} "+
"MERGE (person)-[r:MEMBER_OF]->(group) " +
"SET r.uuid ={2} , r.date={3} " +
"RETURN r")
GroupMembership isMemberOf(String personUuid,String groupUuid, String uuid, Date date);
by calling it like this:
personRepository.isMemberOf(personUuid,groupUuid,uuid,date);
However, don't take it for granted. I haven't done extensive tests to ensure that this approach is thread-safe.
This answer by William Lyon on atomic execution of MERGE may be an extra step that you have to take.

Followee <- Follower relationship Spring-data Neo4j

I have a domain model like Twitter where a USER can FOLLOW other USERs. My class looks like this,
#NodeEntity
public class UserNodeEntity {
#GraphId
Long id;
#Property(name = "firstName")
private String firstName;
#Property(name = "lastName")
private String lastName;
#Relationship(type = "FOLLOWS", direction = Relationship.INCOMING)
private Set<UserNodeEntity> followers = new HashSet<>();
#Relationship(type = "HAS_ROLE", direction = Relationship.OUTGOING)
private Set<UserRole> roles = new HashSet<>();
// getters and setters
...
}
My code to add a new follower looks like this,
public void createNewFollower(String id, String followerId) {
UserNodeEntity usr = getUserById(id); //fetch the user by id from Neo4j
UserNodeEntity follower = getUserById(followerId);
if (usr != null) {
usr.getFollowers().add(follower);
userRepo.save(usr);
}
}
And code to add new Role looks like this,
public void assignNewRole(String id, UserRole role) {
UserNodeEntity usr = getUserById(id); //fetch the user by id from Neo4j
if (usr != null) {
usr.getRoles().add(role);
userRepo.save(usr);
}
}
When I call the createNewFollower() method with the ids of follower and followee one relationship (FOLLOWEE <- FOLLOWS <- FOLLOWER) is created as expected. But when I call the assignNewRole() method with the id of user and his role a new relationship is created by name HAS_ROLE and one more FOLLOWS relationship is created between the previous follower and followee. Now the two users follow each other instead of one relationship (FOLLOWEE <- FOLLOWS <- FOLLOWER)
Can anyone help me in understanding why this is happening?
My Neo4j version is 2.3.3 and spring-data-neo4j version is 4.0.0.RELEASE

Neo4j-spring-data(4.1.1) self relationship (parent-child) is duplicated

I am using spring-data-4.1.1 & Neo4j 2.3.2 with ogm annotations
Below is my entity
#NodeEntity(label = "Component")
public class Component extends BaseEntity {
.........
#Relationship(type = Relation.LINK_TO)
private Set<Link> links = new HashSet<>();
#Relationship(type = Relation.PARENT)
private Set<Component> parents = new HashSet<>();
.........
.........
}
And Link class
#RelationshipEntity(type = Relation.LINK_TO)
public class Link extends BaseEntity {
#Property(name = "isSelfLink")
private boolean isSelfLink;
#StartNode
private Component component;
#EndNode
private Component linkComponent;
}
I've removed getter/setter/hashcode/equals for keeping it clean
Now, here is my code to add two component parent/child and a Link
Component parentcomp = new Component(1, name);
Component childcomp = new Component(2, name);
childcomp.getParents().add(parentcomp);
Link link = new Link();
link.setComponent(parentcomp);
link.setLinkComponent(childcomp);
parentcomp.getLinks().add(link);
componentRepository.save(parentcomp,-1);
Now, as per the logic
object parentcomp property 'parent' should be empty
object childcomp property 'parent' should have parentcomp object
And parentcomp property 'links' should have childcomp
(parentcomp)----LINKS_TO---->(childcomp)
(parentcomp)<----PARENT----(childcomp)
Note: My equirement is such that we need two way relationship..
But, below is the result when I load parent or child entity
object parentcomp property 'parent' has both childcomp,parentcomp instead of empty
object childcomp property 'parent' has both childcomp,parentcomp instead of only parentcomp
This behavior persist until a Neo4j sessions clears out internally. After some time(or after app restart) the mappings shows up correctly.
I tried cleaning up the session using neo4joperations.clear() still problem persists. But if I query
match (c:Component)-[:PARENT]->(p) where c.componentId = {0} return p
results are correct.
I am not sure how to solve this problem...

How to save and retrieve nested objects in neo4j using spring data

I have a model where User will have a list of Roles and Role has a list of Permissions. However, even when i save all of them at once - with depth -1 I am unable to retrieve the child nodes from the parent nodes.
ex: user.getRoles() - 2 [role1,role2]
role1.getAssociatedFeature() - 0
But if i get the Role from the DB
Ex : findByRoleName('role1') -> [Role: role1,Display Role,associatedFeatures[2]]
User.java
#NodeEntity
public class User {
#GraphId Long id;
private String name;
private String loginUserName;
#Relationship(type="ROLE")
private Set<Role> associatedRoles = new HashSet<Role>();
}
Role.java
#NodeEntity
public class Role {
#GraphId Long id;
private String roleName;
private String displayRoleName;
#Relationship(type="ACCESS_TO")
private Set<Feature> associatedFeatures = new HashSet<Feature>();
}
Feature.java
#NodeEntity
public class Feature {
#GraphId Long id;
private String featureName;
#Relationship(type="HAS_PERMISSION")
private Set<Permission> permissions = new HashSet<Permission>();
}
#NodeEntity
public #Data class Permission {
#GraphId
Long id;
String permission;
}
I am using Spring data jpa to use the CRUD operations:
<>Repository.java - This will bydefault implement save,update,delete,find
#RepositoryRestResource()
public interface RoleRepository extends GraphRepository<Role>{...}
ServiceImpl.java
#Override
public User create(User u) {
return userRepo.save(u,-1);
}
In my Junit- I am creating a new User entity, and populating the data all the way to permission. But when i fetch the user -> i only get the roles but not the features, permission along the chain.
In the neo4j DB browser, I see that all the nodes are created with appropriate dependency. Any pointers on how to save and traverse through the graph?
The default load depth is 1. This means you'll get the user and the associated roles, but not the role's features or anything deeper in the graph.
You can specify the load depth if the default is not what you want:
userRepo.findOne(user.getId(), 3);
http://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#_fine_grained_control_via_depth_specification

Resources