How to implement bidirectional relationship in SDN 6 - neo4j

How should bidirectional relationships be implemented in SDN 6?
I have the following implementation of two entities and a repository
#Node("UserAccount")
class UserAccount(
#Id
val username: String,
) {
#Relationship(type = "HAS_ACCOUNT")
var person: Person? = null
}
#Node("Person")
class Person(
#Relationship(type = "HAS_ACCOUNT")
var userAccount: UserAccount? = null,
) {
#Id
#GeneratedValue
var personId: Long = -1
}
#Repository
interface PersonRepository : Neo4jRepository<Person, Long> {
fun findByPersonId(personId: Long): Optional<Person>
}
After fetching person by personId the following exception occurs:
org.springframework.data.mapping.MappingException: The node with id 646 has a logical cyclic mapping dependency. Its creation caused the creation of another node that has a reference to this.

The problem is rooted in the dependency chain (cycle) Person -> UserAccount -> Person, as you have already discovered.
Spring Data Neo4j has a dead-lock detection during mapping to avoid stack overflow / infinite recursion if it comes to cyclic mapping dependencies. This is why you see the exception.
What happens in detail?
SDN starts mapping the Person that depends on an UserAccount instance (constructor) and puts it in a set of "objects in construction".
The UserAccount instance gets created AND the properties of it populated. This has to be done because a property can -besides public visibility and setter- also be "set" by using a wither method (https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#mapping.fundamentals.property-population) and would result in a new (and different instance of the UserAccount)
The person property needs the Person-in-construction -> Exception
To avoid this, you can either remove the constructor dependency and define a field.
Another option is to discard the bi-directional relationship definition completely.

Related

How to persist a List<CustomObject> as a property of a node?

I am trying to persist a list of objects of a class suppose xyz. when I do this in the NodeEntity Class:
#Property
List<xyz> listOfConditions
The Node table when loaded from the neo4j-database via the Neo4jOperations.load(entity) method, will return an error saying :- ERROR mapping GraphModel to NodeEntity type class.
Is there any way to persist a List of Objects onto a nodes properties in Neo4j?. I am using neo4j-ogm-embedded driver and Spring-data-neo4j.
Neo4j does not support storing another object as a nested property. Neo4j-OGM only supports
any primitive, boxed primitive or String or arrays thereof,
essentially anything that naturally fits into a Neo4j node property.
If you want to work around that, you may need to create a custom type convertor. For example,
import org.neo4j.ogm.typeconversion.AttributeConverter
class XYZ{
XYZ(Integer x, String y) {
this.x = x
this.y = y
}
Integer x
String y
}
public class XYZConverter implements AttributeConverter<XYZ, String> {
#Override
public String toGraphProperty(XYZ value) {
return value.x.toString() + "!##" + value.y
}
#Override
public XYZ toEntityAttribute(String value) {
String[] split = value.split("!##")
return new XYZ(Integer.valueOf(split[0]), split[1])
}
}
You can then annotate the #NodeEntity with a #Convert like this
#NodeEntity
class Node {
#GraphId
Long id;
#Property
String name
#Convert(value = XYZConverter.class)
XYZ xyz
}
On the flip side, its not a good practice to do this, since ideally you should link Node and XYZ with a 'hasA' relationship. Neo4j has been designed to optimally handle such kind of relationships, so it would be best to play with to strengths of neo4j
No, nested objects represented as properties on a single node are not supported by the OGM. The only option is to write a custom converter to serialize the nested object to a String representation and store it as a single property.
Otherwise, a list of objects on a node is treated as relationships from the node to those objects.
Here's a link to the manual for further reference: http://neo4j.com/docs/ogm-manual/current/

Spring Data Neo4j 4 very slow for large hierarchical queries

I am currently using SpringDataNeo4j–4.1.0.M1 and have a use case where I need to get a sub-graph (actually a tree) and return it in an hierarchical structure similar to this (these sub-graphs are usually around 500 nodes in size and have an unspecified depth):
<Pathway dbId="1" displayName="Name1">
<Pathway dbId="2" displayName="Name2">
<Pathway dbId="3" displayName="Name3">
<Reaction dbId="4" displayName="Name4" />
<Reaction dbId="5" displayName="Name5" />
<Pathway dbId="6" displayName="Name6">
<Reaction dbId="7" displayName="Name7" />
</Pathway>
</Pathway>
...
The data model:
#NodeEntity
public abstract class Event {
#GraphId
private Long id;
private Long dbId;
private String displayName;
... other relationships and properties
}
#NodeEntity
public class Pathway extends Event {
#Relationship(type="hasEvent", direction = Relationship.OUTGOING)
private List<Event> hasEvent;
... other relationships and properties
}
#NodeEntity
public class Reaction extends Event {
... other relationships and properties
}
To solve this I have created the following query:
String query = "Match (n:Pathway{dbId:123456})-[r:hasEvent*]->(m:Event) Return n,r,m";
Result result = neo4jTemplate.query(query,Collections.<~>emptyMap());
return result.iterator().next().get("n");
This is working and returns the result as expected. The only problem is the speed. While testing the query using the Neo4j-Browser I get a result within 50-100ms. Using SDN it takes over 2 seconds for Spring to map the result to objects.
The question now is: Is there any way I can speed up this query in Spring or is there a better solution where I can return this hierarchical structure in Cypher using something like nested Collections(since I only need the names and ids and not the objects themselves)?

How to use exists, findOne and other GraphRepository methods, taking id as argument?

Let us assume following:
1). We have two entities:
#NodeEntity
public class A {
#GraphId
private Long id;
...
}
#NodeEntity
public class B {
#GraphId
private Long id;
...
}
2). We have two appropriate repositories:
public interface ARepository extends GraphRepository<A> {
...
}
public interface BRepository extends GraphRepository<B> {
...
}
3). And we have following nodes in database:
A with id=1
B with id=2
Case a: Now assume we need to check whether A with id=2 is exists:
ARepository.exists(2)
I expect that it will return false, since A with id=2 isn't exists. But it returns true, since it tries to find among all ids, not only among A nodes.
Case b: Or maybe we need to find B with id=1:
BRepository.findOne(1);
I expect that it returns null or throws appropriate exception, informing that B with id=1 isn't exists. But it throws unexpected for me PersistentEntityConversionException.
Hence questions:
How can we use aforementioned operations with expected behavior?
May be there is workaround?

Do spring data neo4j repositories not distinguish types?

This is a surprise. The Spring-Data-Neo4j GraphRepository code doesn't appear to be able to distinguish between objects of different types.
If I query a repository for an id which is a different type of object I would have expected it not to exist, and not be be loaded - but my test below shows that the repository doesn't appear to be qualifying the exist/findOne call with the object type and so is erroneously loading a B instance as if it's an A.
Is this a bug or a feature?
#NodeEntity class A { #GraphId Long id }
interface ARepository extends GraphRepository<A> {}
#NodeEntity class B { #GraphId Long id }
interface BRepository extends GraphRepository<B> {}
def "objects of different types should not leak between repositories"() {
given: "an A and B object in the neo4j database"
def a = new A()
def b = new B()
aRepository.save(a)
bRepository.save(b)
assert a.id != b.id
when: "the A repository is queried for a B-id"
def exists = aRepository.exists(b.id)
def result = aRepository.findOne(b.id)
then: "it should not be exist, and should not be loaded"
!exists // This assertion fails
!result // This assertion also fails
}
It looks like delete similarly can delete objects of different types. I would have thought that all CRUD method would have been qualified by the label associated with the domain object that the repository is working for.
There is a typesafety strategy/policy in SDN for controlling that behavior:
<bean id="typeSafetyPolicy" class="org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy">
<constructor-arg type="org.springframework.data.neo4j.support.typesafety.TypeSafetyOption"><value>RETURNS_NULL</value></constructor-arg>
</bean>
or
#Bean TypeSafetyPolicy typeSafetyPolicy() {
return new TypeSafetyPolicy(TypeSafetyOption.RETURNS_NULL);
}

Basic problems to use Vaadin 7 + Grails 2.3 (Persistence, domain class design, get Hibernate Session)

I'm using Vaadin 7 + Grails 2.3, there is some questions
My domain classes
class Base {
private static final Date NULL_DATE = new Date(0)
Date createdAt = NULL_DATE;
Date updatedAt = NULL_DATE;
def beforeInsert(){
createdAt = new Date();
updatedAt = new Date();
}
def beforeUpdate(){
updatedAt = new Date();
}
static mapping = {
autoTimestamp true
}
}
abstract class Person extends Base{
String name;
String name2;
String phone1;
String phone2;
static constraints = {
name2 nullable:true
phone1 nullable:true
phone2 nullable:true
}
}
class Customer extends Person {
double credit;
}
THE PROBLEMS
PROBLEM 1
In my Vaadin class UI, if I try this
class MyUI extends UI {
#Override
protected void init(VaadinRequest vaadinRequest) {
Customer customer = new Customer()
customer.name="RODRIGO"
customer.save()
}
}
Show this error
Servlet.service() for servlet [VaadinServlet 0] in context with path [/AgileWeb] threw exception [com.vaadin.server.ServiceException: groovy.lang.MissingPropertyException: No such property: name for class: agileweb.Customer
Possible solutions: all] with root cause
Message: No such property: name for class: agileweb.Customer
Possible solutions: all
there is no "name" property? The class Customer extends Person that has this property.
PROBLEM 2
If I try this
Customer customer = new Customer()
Customer.setName("RODRIGO")
Customer.save()
Show thos error : Message: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
I have seach about this error but I didn't understand to fix it, maybe I'm new with Grails
PROBLEM 3 - ACTUALLY IS A QUESTION
I know that I can use BeanItemContainer, just from List object, that is possible with no problem, but BeanItemContainer is not lazy load, I'd like to use HbnContainer addon (https://vaadin.com/directory#addon/hbncontainer) becase it just need a hibernate session, so How Can I get the "Hibernante session", is there any example and how to do it?
PROBLEM 4 - ACTUALLY IS A QUESTION (AGAIN)
Following this tutorial https://vaadin.com/wiki/-/wiki/Main/Vaadin%20on%20Grails%20-%20Database%20access
It works to save object in the database, but the questions
- Is it really necessary to create a service for each domain class? I have read that it's recomend to put domain logical in the services, I agree with this, but what about simple domain that no need any logical?
so, is there possible to create something like DAO for services? is there any service design to avoid repeted code just to save objects?
I know that are many questions but I think these questions are the same of others, I really want to use Vaadin + Grails to enjoy the better of both, but is not easy to me at the moment!
Before I start answering your question, let me question you domain model. Base class is generally fine, but I want to talk about Person and Customer. You may have good reasons why you picked up inheritance, but please think of composition. Maybe you could have Person that contains a reference to an enum, that states type of the person. You could start here with that: http://en.wikipedia.org/wiki/Composition_over_inheritance
I think you have a typo there. Call save() method on 'customer' not 'Customer', which is a class
When there is a request coming to Grails application, it opens a session and the session is available during that request. There is not this kind of behavior like that in Vaadin. You need to put it into a Service. Yes, you can make generic service to save an object
class GenericService { def save(def domain) { domain.save(failOnError:true) }}
You can get the session factory like this
import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
def ctx = AH.application.mainContext
def sessionFactory = ctx.sessionFactory
or
ApplicationContext applicationContext = grailsApplication.mainContext
ConfigurableLocalSessionFactoryBean factory = applicationContext.getBean('&sessionFactory')
As I wrote, you could create GenericService or a service per domain object. Just to keep in mind that GenericService should only save an object and contain no other logic that would be specific for a domain object.

Resources