Neo4j RelationshipEntity and Spring JPA - neo4j

I have the following nodes and relationships defined:
CarMaker and Models
A CarModel is made CarMaker in multiple years, and that is represented as a property of the MADE_IN relationship.
A CarModel is made by one CarMaker only.
A CarMaker can make multiple CarModels in multiple years.
I have defined the following Classes to represent the nodes: CarModel, CarMaker and the relationship MADE_IN
CarModel
#NodeEntity
public class CarModel {
private Long id;
private String name;
#Relationship (type="MADE_IN", direction = Relationship.UNDIRECTED)
private Set<MadeIn> madeIns = new HashSet<MadeIn>();
private Set<String> years = new HashSet<String>();
public CarModel() {
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addMadeIn(MadeIn madeIn) {
System.out.println ("Found CarMaker: " + madeIn.getCarMaker());
this.madeIns.add(madeIn);
}
private Set<MadeIn> getMadeIn() {
return madeIns;
}
public Set<String> getYears() {
Iterator<MadeIn> itr = madeIns.iterator();
while (itr.hasNext()) {
years.add(((MadeIn) itr.next()).getYear());
}
Set<String> sortedYears = years.stream().collect(Collectors.toCollection(TreeSet::new));
return sortedYears;
}
}
CarMaker
public class CarMaker {
#GraphId private Long id;
private String name;
#Relationship (type="MADE_IN", direction = Relationship.UNDIRECTED)
private Set<CarModel> carModels = new HashSet<>();
public CarMaker() {
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<CarModel> getCarModels() {
return carModels;
}
public void setCarModels(CarModel carModel) {
carModels.add(carModel);
}
}
MADE_IN
#RelationshipEntity(type="MADE_IN")
public class MadeIn {
#GraphId private Long relationshipId;
#Property private String year;
#StartNode private CarMaker carMaker;
#EndNode private CarModel carModel;
public MadeIn() {
}
public MadeIn(CarMaker carMaker, CarModel carModel, String year) {
this.carMaker = carMaker;
this.carModel = carModel;
this.year = year;
}
public Long getRelationshipId() {
return relationshipId;
}
public void setCarMaker(CarMaker carMaker) {
this.carMaker = carMaker;
}
public CarMaker getCarMaker() {
return this.getCarMaker();
}
public void setCarModel(CarModel carModel) {
this.carModel = carModel;
}
public CarModel getCarModel() {
return this.getCarModel();
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
}
When I make a request to retrieve a CarModel, I receive a response with the details of that model and all years when it was manufactured:
{
"id": 260248,
"name": "Ulysse",
"years": [
"1994",
"1995",
"1996",
"1997",
"1998",
"1999",
"2000",
"2001",
"2004",
"2005",
"2006",
"2007",
"2008",
"2009",
"2010",
"2011",
"2012"
]
}
The problem is when I try to request the CarModels made by a CarMaker:
{
"id": 4152072,
"name": "BMW",
"carModels": []
}
I noticed that if I reverse the annotations #StartNode and #EndNode on the MadeIn class I get the information about the CarModels made by a CarMaker, however I will not longer get the information about the years when those models were made.
{
"id": 4152072,
"name": "BMW",
"carModels": [
{
"id": 260852,
"name": "120",
"years": []
},
{
"id": 261430,
"name": "Z18",
"years": []
},
{
"id": 262044,
"name": "L7",
"years": []
},
Any idea on what am I missing, or what I am doing wrong ?
Thanks in advance for any help.
--MD

Related

Micronaut does not map an object in a multipart

So.. I have a method that consumes multipart/form-data. I'm trying to pass an object named User (ignoring some fields), and user avatar file
#ExecuteOn(TaskExecutors.IO)
#Operation(summary = "Endpoint for user registration")
#Post(uri = "/register",consumes = {MediaType.MULTIPART_FORM_DATA},produces = MediaType.APPLICATION_JSON)
#Requires(bean = User.class)
public HttpResponse<DefaultAppResponse> register(
#Part("credential") User credential,
#Part("avatar") #Nullable CompletedFileUpload avatar
){
try {
if(avatar != null) {
Files saved = filesService.save(avatar, dirPattern + avatars);
saved.setOid(transactionalRepository.genOid().orElseThrow());
filesRepository.save(saved);
credential.setAvatarPath(saved);
}
credential.setUserRegDate(new Date(System.currentTimeMillis()));
userRepository.save(credential);
return HttpResponse.ok(
errorService.success()).status(201
);
} catch (Exception e) {
registerLog.error(e.getMessage(), e.getStackTrace());
throw new InternalExceptionResponse("Error: " +e.getMessage() , errorService.error("error: " +e.getMessage()));
}
}
User.class
#Entity
#Table(name = "users", schema = "public")
#Introspected
#JsonView(Default.class)
public class User extends BaseEntity{
#JsonInclude
#JsonProperty("user_name")
#Column(name = "user_name")
#JsonAlias("userName")
private String userName;
#JsonInclude
#JsonProperty("user_birthday")
#Column(name = "user_birthday")
#JsonAlias("userBirthday")
#JsonFormat(pattern = "yyyy-MM-dd")
private Date userBirthday;
#JsonInclude
#JsonFormat(pattern = "yyyy-MM-dd")
#JsonProperty("user_reg_date")
#Column(name = "user_reg_date")
#JsonAlias("userRegDate")
private Date userRegDate;
#JsonInclude
#JsonProperty("user_email")
#Column(name = "user_email")
#JsonAlias("userEmail")
private String userEmail;
#JsonProperty("user_password")
#Column(name = "user_password")
#JsonAlias("userPassword")
#JsonView(WithPassword.class)
private String userPassword;
#ManyToOne
#JoinColumn(name = "avatar_path")
#JsonProperty("avatar_path")
#JsonInclude
#JsonAlias("avatarPath")
private Files avatarPath;
#JsonInclude
#JsonProperty("user_phone_number")
#Column(name = "user_phone_number")
#JsonAlias("userPhoneNumber")
private String userPhoneNumber;
#JsonInclude
#JsonProperty("user_is_confirm")
#Column(name = "user_is_confirm")
#JsonAlias("userIsConfirm")
private Boolean userIsConfirm;
public User(
String oid, String userName,
Date userBirthday, Date userRegDate, String userEmail, String userPassword, Files avatarPath,
String userPhoneNumber, Boolean userIsConfirm
) {
super(oid);
this.userName = userName;
this.userBirthday = userBirthday;
this.userRegDate = userRegDate;
this.userEmail = userEmail;
this.userPassword = userPassword;
this.avatarPath = avatarPath;
this.userPhoneNumber = userPhoneNumber;
this.userIsConfirm = userIsConfirm;
}
public User() {
}
public Files getAvatarPath() {
return avatarPath;
}
public void setAvatarPath(Files avatarPath) {
this.avatarPath = avatarPath;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
public Date getUserRegDate() {
return userRegDate;
}
public void setUserRegDate(Date userRegDay) {
this.userRegDate = userRegDay;
}
public String getUserEmail() {
return userEmail;
}
public void setUserEmail(String userEmail) {
this.userEmail = userEmail;
}
public Boolean getUserIsConfirm() {
return userIsConfirm;
}
public void setUserIsConfirm(Boolean userIsConfirm) {
this.userIsConfirm = userIsConfirm;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
public String getUserPhoneNumber() {
return userPhoneNumber;
}
public void setUserPhoneNumber(String userPhoneNumber) {
this.userPhoneNumber = userPhoneNumber;
}
}
But, When I trying to transfer an object, it simply cannot accept / parse / map it (underline correctly) What do I get in response
Request(Swagger)
Response
{
"message": "Bad Request",
"_links": {
"self": {
"href": "/api/reg/register",
"templated": false
}
},
"_embedded": {
"errors": [
{
"message": "Required Part [credential] not specified",
"path": "/credential"
}
]
}
}
If I remove #Part("credentails"), then it will contain an entity with fields equivalent to null.
See screenshot
Debug Evaluation
In my research, I saw that type:application/json is not being passed.But avatar file has type:image/png. See CURL request(generated from swagger)
curl -X 'POST' \
'http://localhost:8080/api/reg/register' \
-H 'accept: application/json' \
-H 'Content-Type: multipart/form-data' \
-F 'credential={
"oid": "null",
"avatar_path": "null"
"user_reg_date": "null",
"user_name": "User FUllName"
"user_birthday": "2001-10-28",
"user_email": "email#gmail.com",
"user_password": "123123",
"user_phone_number": "some-valid-phone-number"
}' \
-F 'avatar=#sticker.png;type=image/png'
Question: what could be the problem?

Improve relationship performance using Spring Data Neo4J

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.

Creating relationships between nodes using Spring Data Rest and neo4j

I am starting to use Neo4J with Spring Data Rest. I have a node entity and a relationship entity for modelling nodes and edges. I'm able to create new nodes with the following using postman.
POST http://localhost:8080/nodes
{
"name" : "Test"
}
I am unsure of what the JSON format would be to create relationships between the nodes. For example:
Create a new node and relate to an existing node
Create a relationship between two existing nodes.
Any examples on what JSON I need to use would be very much appreciated.
My node entity and relationship entity are as follows:
#NodeEntity
public class Node {
#Id
#GeneratedValue
private Long id;
private String name;
private int count;
#Relationship(type = Edge.TYPE, direction = Relationship.UNDIRECTED)
private Set<Edge> edges = new HashSet<>();
public void addEdge(Node target, int count) {
this.edges.add(new Edge(this, target, count));
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<Edge> getEdges() {
return edges;
}
public void setEdges(Set<Edge> edges) {
this.edges = edges;
}
}
#RelationshipEntity(type = Edge.TYPE)
public class Edge {
public static final String TYPE = "LINKED_TO";
#Id
#GeneratedValue
private Long relationshipId;
#StartNode
private Node start;
#EndNode
private Node end;
private int count;
public Edge() {
super();
}
public Edge(Node start, Node end, int count) {
this.start = start;
this.end = end;
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Node getStart() {
return start;
}
public void setStart(Node start) {
this.start = start;
}
public Node getEnd() {
return end;
}
public void setEnd(Node end) {
this.end = end;
}
public Long getRelationshipId() {
return relationshipId;
}
public void setRelationshipId(Long relationshipId) {
this.relationshipId = relationshipId;
}
}
OK I worked it out, you can do this:
PATCH http://localhost:8080/nodes/1
{
"name" : "Test",
"edges": [
{
"start": "http://localhost:8080/nodes/1",
"end": "http://localhost:8080/nodes/2"
}
]
}
This will add the relationships between the nodes.
Hope this helps someone.

GORM is not saving my hasMany entity

So I am asking a lot about GORM lately because it's the first time I am using it, and each time I have some issues with relations between objects and saving them.
So this is one class:
class TesterUser {
#Id
private String id
private String userId
static belongsTo = Dashboard
static constraints = {
userId nullable: true
}
static mapping = {
id column: 'id', generator: 'assigned'
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
And this is the other class:
class TestingClass {
#Id
private String id
private Date created
private Date modified
private String title
private ClassName className
static hasMany = [testUsers : TesterUser, sheets : Sheet]
static belongsTo = ClassName
static constraints = {
modified nullable: true
title nullable: true
className nullable: true
}
static mapping = {
sheets column:'testingClassId',joinTable: false
testUsers column:'testingClassId',joinTable: false
id column: 'id', generator: 'assigned'
title column: "title", length: 90000
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getModified() {
return modified;
}
public void setModified(Date modified) {
this.modified = modified;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public DavUser getClassName() {
return className;
}
public void setClassName(ClassName className) {
this.className = className;
}
public Date getDeleted() {
return deleted;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
I already saved the objects in the DB, but now I want to set the relations between them and when I call and save them it's not working:
TesterUser testU = TesterUser.findById(uId)
TestingClass testC = TestingClass.findById(cId)
if(testU != null && testC != null){
amountOfRelations++
testC.addToDashboardUsers(testU)
if(!dtestC.save(flush:true, failOnError: true)){
amountOfUnsaved++
}
else{
amountOfsaved++
}
For some reason, I get no error. Not only that, I can see the queries are going to my db, but nothing happens. There is no update and no error.
I have no idea why it's not working.
Any idea?
Eventually I was not able to save my entities.
My 'solution' was to save the entity in the first place using addTo method.
I still don't know why it didn't work or what I needed to do in order to make my entity to be saved, but still I got a workaround.

java.lang.StackOverFlow in Primefaces's treeTable

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.

Resources