Can relate to only one element - neo4j

I am using Neo4j OGM 2.0.4 driver with Java. I have trouble with adding more than one relationship to element.
I do something like this:
Site site1 = new Site();
site1.setTitle("Site 1");
site1.setHtmlCode("Content of site 1");
Site site2 = new Site();
Site subsite1 = new Site();
subsite1.setTitle("Subsite 1");
subsite1.setHtmlCode("Content of subsite 1");
subsite1.setParent(site1);
Site subsite2 = new Site();
subsite2.setTitle("Subsite 2");
subsite2.setHtmlCode("Content of subsite 2");
subsite2.setParent(site1);
session.deleteAll(Site.class);
session.save(site1);
session.save(subsite1);
session.save(subsite2);
When I want to show all Site nodes (on localhost:7474) then "Subsite 1" has no relationship.
#NodeEntity
public class Site extends Entity
{
private String _title;
private String _htmlCode;
#Relationship(type = "SITE_CREATED_BY")
Author _author;
#Relationship(type = "IS_CHILD")
Set<Site> _parentSite;
#Relationship(type = "IS_CHILD", direction = Relationship.INCOMING)
Set<Site> _childSites;
public Site()
{
_parentSite = new HashSet();
_childSites = new HashSet();
}
public void setTitle(String title)
{
_title = title;
}
public String getTitle()
{
return _title;
}
public void setHtmlCode(String htmlCode)
{
_htmlCode = htmlCode;
}
public String getHtmlCode()
{
return _htmlCode;
}
public void setAuthor(Author author)
{
_author = author;
}
public void setParent(Site site)
{
_parentSite.add(site);
}
}
Entity:
public abstract class Entity
{
private Long id;
private final ZonedDateTime _dateOfCreation;
Entity()
{
_dateOfCreation = ZonedDateTime.now();
}
public Long getId()
{
return id;
}
public ZonedDateTime getDateOfCreation()
{
return _dateOfCreation;
}
#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;
return id.equals(entity.id);
}
#Override
public int hashCode()
{
return (id == null) ? -1 : id.hashCode();
}
}
What am I doing wrong?

In this case where you have two relationships in different directions between the same type of node, first, make sure that you annotate both the fields as well as setter/accessor methods with the #Relationship,specifying the direction.
Site in your object model has references to both the parent and children, but when you create sites, they do not seem consistent with the model. Subsite1 and Subsite2 both set their parents to site1 but site has no record of its children (should be both subsites). Should work if your object and graph models are consistent.

Related

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.

Join table issue in Spring Data JPA

I am trying to create a view with datas which combines two tables. I successfully implemented the join and datas are displaying properly by using spring data JPA join. Here my issue is that, when I am calling findAll() method from only one table, which returns all the data including joined table also,
I joined table Users model class like:
#Entity
#Table(name = "users")
public class Users implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "username")
public String username;
#Column(name = "password")
public String password;
#Column(name = "privid")
public Integer privid;
#OneToMany(cascade = CascadeType.ALL,mappedBy="pid")
public Set<Privillages> priviJoin;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getPrivid() {
return privid;
}
public void setPrivid(Integer privid) {
this.privid = privid;
}
public Set<Privillages> getPriviJoin() {
return priviJoin;
}
public void setPriviJoin(Set<Privillages> priviJoin) {
this.priviJoin = priviJoin;
}
public Users() {
}
}
And my second model Privillages like,
#Entity
#Table(name = "Privillages")
public class Privillages implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Integer Id;
#Column(name = "pname")
public String pname;
#ManyToOne(optional = false)
#JoinColumn(name = "pid", referencedColumnName = "privid")
public Users pid;
public Integer getId() {
return Id;
}
public void setId(Integer id) {
Id = id;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public Users getPid() {
return pid;
}
public void setPid(Users pid) {
this.pid = pid;
}
public Privillages(){
}
}
And repository containing,
#Query("select u from Users u JOIN FETCH u.priviJoin p")
Set<Users> findByUsername();
These are all my codes, here i added. The thing is that, join is properly working with expected resultset. But when I call findAll() method , the it returns all the structure including both joined table.
I called my findAll function like,
#RequestMapping("/check")
public List<Users> check() {
return (List<Users>) userRepo.findAll();
}
But result is like I previously mentioned.Here I added its screenshot,
In this figure we can see that it returns the both table values instead of users table data.
Why is it happening like this?
You defined your domain type Users to contain a reference so it gets loaded as specified.
If you want something similar to a Users object but without the reference, you have two options:
Change the Users type to not contain a reference.
Use a different type, similar to Users but without the reference. There are multiple ways to do that, but probably the simplest and most helpful in the current situation is to use a projection. See https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections

Neo4j remove existing relationships itself during adding new relationships

I have a code responsible for creating new relationship between two nodes.
At first It get a two nodes by property (pk), check if such relationship between those nodes already exists and if It doesn't not, create one.
public void createNeo4jGraphLink(#Nonnull final String childPk, #Nonnull final String parentPk) {
final UUID measureId = measureService.startMeasure(MeasureService.MeasureEvent.NEO_PERSIST);
Node child = neoRepository.findOneByPk(childPk);
Node parent = neoRepository.findOneByPk(parentPk);
checkIfLinkingAllowed(child, parent);
if (child.getPartOf() == null || !child.getPartOf().contains(parent)) {
if (child.getPartOf() == null) {
child.setPartOf(new HashSet<>());
}
child.getPartOf().add(parent);
neoRepository.save(child);
}
measureService.fixMeasure(MeasureService.MeasureEvent.NEO_PERSIST, measureId);
}
I can see that number of relationships rises in db, but one moment it sharply decrease and so on.. What reason can lead to this behavior? I've tried to use #Transactional annotation, experimented with propagation and isolation of transaction and tried without transactions at all, but it all does not work. Btw I use Spring Data Neo4j repositories. Spring Boot 1.4.4.RELEASE.
Child and parent entity' type is "Node" which is defined as a follows
#NodeEntity(label = "node")
public class Node {
private String pk = UUID.randomUUID().toString();
#JsonIgnore
private Long id;
private String name;
private Set<Node> partOf;
private String type;
public Node() {
}
#Property(name = "pk")
public String getPk() {
return pk;
}
public void setPk(String pk) {
this.pk = pk;
}
#GraphId(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Property(name = "type")
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#Property(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Relationship(type = "PART_OF", direction = Relationship.OUTGOING)
public Set<Node> getPartOf() {
return partOf;
}
public void setPartOf(Set<Node> partOf) {
this.partOf = partOf;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node node = (Node) o;
if (!pk.equals(node.pk)) return false;
return true;
}
#Override
public int hashCode() {
return pk.hashCode();
}
}
I perform adding the relationship 1800 times, but as result have 62 relationships. Code does not throw any errors. While processing those 1800 requests, I can see in database there is any number of relationships (less then 1800), but as result only 62.
Thank you for any information

Spring Data Neo4J - Create new node with a relationship with an existing node

I need to create a new node of the class A, which has a relationship with the User node:
#NodeEntity
public class A implements Serializable {
/**
* Graph node identifier
*/
#GraphId
private Long nodeId;
/**
* Enity identifier
*/
private String id;
//... more properties ...
#Relationship(type = "HAS_ADVERTISER", direction = Relationship.OUTGOING)
private User user;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof IdentifiableEntity)) return false;
IdentifiableEntity entity = (IdentifiableEntity) o;
if (!super.equals(o)) return false;
if (id != null) {
if (!id.equals(entity.id)) return false;
} else {
if (entity.id != null) return false;
}
return true;
}
}
#NodeEntity
public class User implements Serializable {
/**
* Graph node identifier
*/
#GraphId
private Long nodeId;
/**
* Enity identifier
*/
private String id;
//... more properties ...
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof IdentifiableEntity)) return false;
IdentifiableEntity entity = (IdentifiableEntity) o;
if (!super.equals(o)) return false;
if (id != null) {
if (!id.equals(entity.id)) return false;
} else {
if (entity.id != null) return false;
}
return true;
}
}
Now, let's suppose we have the following data for the new node A:
{
"id": 1,
"nodeId": "0001-0001",
"user": {
"id": 4,
"nodeId": "0002-0002",
"name": null,
"firstName": null
}
}
I'm trying to create the node A with a relationship between the new node A and the (already existing) node of the User who has the "id":4 and "nodeId":"0002-0002" (unique node identifier), but instead of that, the User node updates the fields "name" and "firstName" with to null.
I'm using the GraphRepository proxy to create it:
#Repository
public interface ARepository extends GraphRepository<A> {
}
Is there any way to do it without this update, only making the relationship with the User node?
No, you'll have to either reload the entity by id to populate all missing values and then save, or write a custom query.
You can do it with a MERGE. See the Cypher RefCard
#Repository
public interface ARepository extends GraphRepository<A> {
#Query("MERGE (a:A {id:aId}})-[:HAS_ADVERTISER]-(u:User {id:{userId}})" +
"RETURN a")
public A createAndGetAd(#Param("aId") String aId,
#Param("userId") String userId)
}

How to select combobox by id or value using with BeanItemContainer?

I am using BeanItemContainer for my comboboxes to satisfy key-value pairs.
#SuppressWarnings("serial")
public class ComboBoxItem implements Serializable {
private String id;
private String description;
public ComboBoxItem(final String id, final String description) {
this.id = id;
this.description = description;
}
public final void setId(final String id) {
this.id = id;
}
public final void setDescription(final String description) {
this.description = description;
}
public final String getId() {
return id;
}
public final String getDescription() {
return description;
}
}
I created a sample combobox as below
List<ComboBoxItem> lstAuctionDateList = new ArrayList<ComboBoxItem>();
lstAuctionDateList.add(new ComboBoxItem("all", "All"));
BeanItemContainer<ComboBoxItem> auctionDateItems = new BeanItemContainer<ComboBoxItem>(ComboBoxItem.class,
lstAuctionDateList);
final ComboBox cbAuctionDate = new ComboBox("Auction Date", auctionDateItems);
cbAuctionDate.addStyleName("small");
cbAuctionDate.setNullSelectionAllowed(false);
cbAuctionDate.setTextInputAllowed(false);
cbAuctionDate.setItemCaptionPropertyId("description");
cbAuctionDate.addValueChangeListener(new ValueChangeListener() {
public void valueChange(final ValueChangeEvent event) {
if (cbAuctionDate.getValue() != null) {
System.out.println(((ComboBoxItem) cbAuctionDate.getValue()).getId());
System.out.println(((ComboBoxItem) cbAuctionDate.getValue()).getDescription());
}
}
});
It is fine but I can't select any of combobox items by using below codes
cbAuctionDate.select("all");
cbAuctionDate.select("All");
cbAuctionDate.setValue("all");
cbAuctionDate.setValue("All");
What am I wrong ? How can I select my comboxes by programmatically ?
when using a (bean) container and adding items, the identity of the item itself is used as the itemId in the container. E.g. cbActionDate.select(lstAuctionDateList[0]) should work.
You either have yo make your objects immutable or use ways to tell the container, what it has to use for an id (E.g. setBeanIdProperty("id") or setBeanIdResolver).
Making the object immutable should be easy right now (make the class and the private attributes final, drop the setters and let your IDE generate equals and hashCode for you)
You don't need the cbAuctionDate.addItem("All") call, you already have such a item in your collection
I would try it that way:
List<ComboBoxItem> lstAuctionDateList = new ArrayList<ComboBoxItem>();
ComboBoxItem allItems= new ComboBoxItem("all", "All");
lstAuctionDateList.add(allItems);
....
...
cbAuctionDate.select(allItems);
Now I created custom ComboBox component for my problem
public class ComboBox extends CustomComponent implements Serializable {
private com.vaadin.ui.ComboBox comboBox;
private BeanItemContainer<ComboBoxItem> entries = new BeanItemContainer<ComboBoxItem>(ComboBoxItem.class);
public ComboBox() {
comboBox = new com.vaadin.ui.ComboBox();
comboBox.addStyleName("small");
comboBox.setNullSelectionAllowed(false);
comboBox.setTextInputAllowed(false);
setCompositionRoot(comboBox);
}
public ComboBox(final String caption) {
comboBox = new com.vaadin.ui.ComboBox();
comboBox.addStyleName("small");
comboBox.setNullSelectionAllowed(false);
comboBox.setTextInputAllowed(false);
setCaption(caption);
setCompositionRoot(comboBox);
}
public ComboBox(final String caption, final List<ComboBoxItem> items) {
comboBox = new com.vaadin.ui.ComboBox();
comboBox.addStyleName("small");
comboBox.setNullSelectionAllowed(false);
comboBox.setTextInputAllowed(false);
setCaption(caption);
if (items != null && items.size() > 0) {
entries.addAll(items);
comboBox.setContainerDataSource(entries);
comboBox.setItemCaptionMode(ItemCaptionMode.PROPERTY);
addItems(entries);
comboBox.select(items.get(0));
comboBox.setItemCaptionPropertyId("description");
}
setCompositionRoot(comboBox);
}
public final void addItems(final List<ComboBoxItem> items) {
if (items != null && items.size() > 0) {
entries.addAll(items);
comboBox.setContainerDataSource(entries);
comboBox.setItemCaptionMode(ItemCaptionMode.PROPERTY);
addItems(entries);
comboBox.select(items.get(0));
comboBox.setItemCaptionPropertyId("description");
}
}
private void addItems(final BeanItemContainer<ComboBoxItem> items) {
comboBox.addItems(items);
}
public final void addItem(final ComboBoxItem item) {
if (item != null) {
comboBox.setContainerDataSource(entries);
comboBox.addItem(item);
comboBox.setItemCaptionPropertyId("description");
}
}
public final void selectByIndex(final int index) {
Object[] ids = comboBox.getItemIds().toArray();
comboBox.select(((ComboBoxItem) ids[index]));
}
public final void selectById(final String id) {
Object[] ids = comboBox.getItemIds().toArray();
for (int i = 0; i < ids.length; i++) {
if (((ComboBoxItem) ids[i]).getId().equals(id)) {
selectByIndex(i);
break;
}
}
}
public final void selectByItemText(final String description) {
Object[] ids = comboBox.getItemIds().toArray();
for (int i = 0; i < ids.length; i++) {
if (((ComboBoxItem) ids[i]).getDescription().equals(description)) {
selectByIndex(i);
break;
}
}
}
public final int getItemCount() {
return comboBox.getItemIds().toArray().length;
}
public final String getSelectedId() {
return ((ComboBoxItem) comboBox.getValue()).getId();
}
public final String getSelectedItemText() {
return ((ComboBoxItem) comboBox.getValue()).getDescription();
}
public final void addValueChangeListener(final ValueChangeListener listener) {
comboBox.addValueChangeListener(listener);
}
}
and below is test codes
final ComboBox combo = new ComboBox("My ComboBox");
combo.addItem(new ComboBoxItem("all", "All"));
// Add with list
List<ComboBoxItem> items = new ArrayList<ComboBoxItem>();
items.add(new ComboBoxItem("one", "One"));
items.add(new ComboBoxItem("two", "Two"));
items.add(new ComboBoxItem("three", "Three"));
combo.addItems(items);
combo.addItem(new ComboBoxItem("four", "Four"));
combo.addItem(new ComboBoxItem("five", "five"));
combo.selectByIndex(3);
combo.addValueChangeListener(new ValueChangeListener() {
public void valueChange(final ValueChangeEvent event) {
System.out.println(combo.getSelectedId() + " --- " + combo.getSelectedItemText());
}
});

Resources