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

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;
}

Related

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

#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.

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

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

Issues while retrieving existing nodes using Spring Data Neo4j

I created a simple SDN project to retrieve existing nodes from my database. In the repository, I defined a custom query using #Query annotation which is something like
#Query("MATCH (EMP:EMPLOYEE) WHERE EMP.empName={0} return EMP")
public Employee findByName(String empName);
#RelationshipEntity(type = "HAS_ADDRESS")
class AddressRelationShip
{
#GraphId
Long id;
#StartNode
Employee employee = null;
#EndNode
Address address = null;
public AddressRelationShip(Employee employee, Address address)
{
this.employee = employee;
this.address = address;
}
}
#NodeEntity
#TypeAlias("EMPLOYEE")
public class Employee
{
#GraphId
Long id;
String empName = null;
#RelatedTo(type = "HAS_ADDRESS", direction = Direction.OUTGOING)
#Fetch
Set<Address> addresses;
public void addressEmplployee(Address address)
{
if (addresses == null)
{
addresses = new HashSet<Address>();
}
//AddressRelationShip addressRelationShip = new AddressRelationShip(this, address);
addresses.add(address);
}
public Set<Address> getAddresses()
{
return addresses;
}
public void setAddresses(Set<Address> addresses)
{
this.addresses = addresses;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getEmpName()
{
return empName;
}
public void setEmpName(String empName)
{
this.empName = empName;
}
}
With this query, on execution I get the below error message:
No primary SDN label exists .. (i.e one starting with _)
I googled about the issue and tried to use the below query:
MATCH (EMP:EMPLOYEE:_EMPLOYEE) WHERE EMP.EmployeeId={0} return EMP
This query runs but it doesn't return any response.
One important thing here is that I haven't created the existing nodes using SDN(I googled and found that SDN adds some metadata e.g, _ to nodes/relationships).
However, if I created the (Employee)-[HAS_ADDRESS]->(ADDRESS) pattern data using SDN, below query works fine:
MATCH (EMP:EMPLOYEE) WHERE EMP.empName={0} return EMP
In this case, I found one other issue that it returned address data as well while I'm only returning Employee in the query.
I'm able to obtain the addresses from the Employee entity object.
Any pointers on the above issues?
PS - Neo4j is running in standalone server mode.
Regards,
Rahul
I could resolve the above issues in following steps:
No primary SDN label exists .. (i.e one starting with _) - In SDN 3.3.0, for existing nodes, SDN requires an extra label (in my case, _EMPLOYEE), so a data migration is required. In SDN 4.0, It seems that this is no longer needed but I haven't tried 4.0 yet.
Returning address data as well while I'm only returning Employee in the query - Removing #Fetch on Set addresses in Employee resolved this, However, addresses nodeIds were still returned.
To run SDN 3.x.x with existing data, following data migration is required:
Add additional NodeLabel(preceding the original label with _) to nodes, e.g, add _Employee label to all Employee nodes.
Add __type__ property to nodes and relationships whose value will be fully qualified name for the appropriate domain/model classes, e.g,
match (n:Employee) set n.__type__="org.neo4j.domain.Employee"
Cheers,
Rahul

create member profiles programatically in umbraco

I'm trying to create new member programaticaly.
In fact I need to build a function to import members from a spreadsheet.
I build a Member class which inherits ProfileBase and can create one member programaticaly when he registers but that requires he logs in to update full profile.
So a member registers, his membership is created, I log him in, create his profile and log him out. This is not visible for the user but works well in the background.
Now I need to to import members through a feature in the umbraco back office.
I am able to create members but I am unable to update their profiles which use custom properties set up in web.config, umbraco and my Member class. I just get empty profile in my database.
Any idea what I can do in this case? How I can update a member profile without logging the member in?
Thank you
I did something very similar myself recently, assuming your Member class looks something like the following (or at least does the same thing).
public class MemberProfile : ProfileBase
{
#region Firstname
private const string FIRSTNAME = "_firstname";
[SettingsAllowAnonymous(false)]
public string FirstName
{
get
{
return GetCustomProperty(FIRSTNAME);
}
set
{
SetCustomProperty(FIRSTNAME, value);
}
}
#endregion
#region Get and Set base properties
private string GetCustomProperty(string propertyName)
{
var retVal = "";
var prop = base.GetPropertyValue(propertyName);
if (prop != null)
{
retVal = prop.ToString();
}
return retVal;
}
private void SetCustomProperty(string propertyName, object value)
{
var prop = base[propertyName];
if (prop != null)
{
base.SetPropertyValue(propertyName, value);
}
}
#endregion
}
Then using the following method should allow you to retrieve a member, update a property and save them:
var existingMember = MemberProfile.Create(username) as MemberProfile;
existingMember.FirstName = firstName;
existingMember.Save();
The confusing bit is that the Create() method actually returns existing users, they just called it create for some reason...

Resources