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

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/

Related

How to implement bidirectional relationship in SDN 6

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.

Unwinding and merging the results with dynamic labels in Neo4J Client for C#

I am currently trying to unwind a list of objects that I want to merge to the database using the Neo4J Client. What I would like to do is unwind the list and create the nodes with a label generated based on a property from the items themselves instead of hardcoding a label name. From what I can find I have to use the APOC merge method to do so. However, I am unable to translate this to the Neo4J client. In the neo4J explanation they yield a node after the apoc.merge.node call and then return the node. However, I cannot simply return the node nor can I set the node (I got to the point of just messing about, and at one point I got the labels to work but it overwrote all properties with the last item in the list).
I seem to miss something fundamental but i'm not quite sure what. Does anyone here know how to do this with neo4J client (and if possible, give a bit of an explanation what is going on)? I am very new to the development world and I feel I am just missing a crucial piece of understanding when it comes to this..
The code that I tried that turned all properties into the last node's properties but at least created the labels as I expected:
public async void CreateBatchItems(List<TToDataBase> itemList)
{
await Client.Cypher
.Unwind(itemList, "row")
.Merge("(n)")
.With("row, n")
.Call("apoc.merge.node([n.Name], n)").Yield("node")
.Set("n += node")
.ExecuteWithoutResultsAsync();
}
Thank you in advance!
Edit:
Some clarification about the input:
The objects are actually very basic, as (at least for now), they merely contain a name and an objectID (and these object ID's are later used to create relations). So its a very basic class with two properties:
public class Neo4JBaseClass
{
public Neo4JBaseClass() { }
public Neo4JBaseClass(string name, string objectId)
{
Name = name;
ObjectId = objectId;
}
[JsonProperty(PropertyName = "ObjectId")]
public string ObjectId { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
}
I have also tried a slight variation where this class also has the added property
[JsonProperty(PropertyName = "PropertyMap")]
public IProperty PropertyMap { get; set; }
where PropertyMap is another basic object holding the name and objectId. This seemed like a good idea for future proofing anyway, so the propertylist can be easily expanded without having to change the base object.
[EDITED]
The main issue is that Merge("(n)") matches any arbitrary node that already exists.
You have not shown the data structure for each element of itemList, so this answer will assume it looks like this:
{Name: 'SomeLabel', id: 123, Props: {foo: 'xyz', bar: true}}
With above data structure, this should work:
public async void CreateBatchItems(List<TToDataBase> itemList)
{
await Client.Cypher
.Unwind(itemList, "row")
.Call("apoc.merge.node([row.ObjectId], row.id)").Yield("node")
.Set("node += row.Props")
.ExecuteWithoutResultsAsync();
}
[UPDATE]
The data structure you added to your question is very different than what I had imagined. Since neither of the properties in a row is a map, .Set("node += row.Props") would generate an error.
Using your data structure for each row, this might work:
public async void CreateBatchItems(List<TToDataBase> itemList)
{
await Client.Cypher
.Unwind(itemList, "row")
.Merge("(n:Foo {id: row.ObjectId})")
.Set("n += row.Name")
.ExecuteWithoutResultsAsync();
}
This code assigns the node label Foo to all the generated nodes. A node should always have a label, which improves clarity and also tends to improve efficiency -- especially if you also create indexes. For example, an index on :Foo(id) would make the above query more efficient.
This code also assumes that the id property is supposed to contain a unique Foo node identifier.

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)?

IBM Integration Bus: How to read user defined node (Java) complex (table) property in Java extension code

I created Java user defined node in IntegrationToolkit (9.0.0.1) and assigned it with several properties. Two of the node properties are simple (of String type) and one property is complex (table property with predefined type of User-defined) that is consisted of another two simple properties.
By following the documentation I was able to read two simple properties in my Java extension class (that extends MbNode and implements MbNodeInterface) by making getters and setters that match the names of the two simple properties. Documentation also states that getters and setters should return and set String values whatever the real simple type of a property may be. Obviously, this would not work for my complex node property.
I was also able to read User Defined Properties that are defined on the message flow level, by using CMP (Integration Buss API) classes, which was another impossible thing to do from user defined node without CMP. At one point I began to think that my complex property would be among User Defined Properties, (although UDPs are defined on the flow level and my property is defined on the custom node level) based on some other random documentation and some other forum discussion.
I finally deduced that the complex property should map to MbTable type (as it is so stated in that type's description), but I was not able to use that.
Does anyone know how to access user defined node's complex(table) property value from Java?
I recently started working with WebSphere Message Broker v 8.0.0.5 for one of my projects and I was going to ask the same question until SO suggested your question which answered my question. It might be a little late for this question but it may help others having similar questions.
After many frustrating hours consulting IBM documentation this is what I found following your thread:
You're correct about the properties being available as user-defined properties (UDP) but only at the node level.
According to the JavaDoc for MbTable class (emphasis added to call out the relevant parts):
MbTable is a complex data type which contains one or more rows of simple data types. It structure is very similar to a * standard java record set. It can not be constructed in a node but instead is returned by the getUserDefinedAttribute() on the MbNode class. Its primary use is in allowing complex attributes to be defined on nodes instead of the normal static simple types. It can only be used in the runtime if a version of the toolkit that supports complex properties is being used.
You have to call com.ibm.broker.plugin.MbNode.getUserDefinedAttribute which will return an instance of com.ibm.broker.plugin.MbTable. However, the broker runtime doesn't call any setter methods for the complex attributes during the node initialization process like it does for simple properties. Also, you cannot access the complex attributes in either the constructor or the setter methods of other simple properties in the node class. These are available only in the run or evaluate method.
The following is the decompiled method definition of com.ibm.broker.plugin.MbNode.getUserDefinedAttribute.
public Object getUserDefinedAttribute(String string) {
Object object;
String string2 = "addDynamicTerminals";
if (Trace.isOn) {
Trace.logNamedEntry((Object)this, (String)string2);
}
if ((object = this.getUDA(string)) != null && object.getClass() == MbElement.class) {
try {
MbTable mbTable;
MbElement mbElement = (MbElement)object;
object = mbTable = new MbTable(mbElement);
}
catch (MbException var4_5) {
if (Trace.isOn) {
Trace.logStackTrace((Object)this, (String)string2, (Throwable)var4_5);
}
object = null;
}
}
if (Trace.isOn) {
Trace.logNamedExit((Object)this, (String)string2);
}
return object;
}
As you can see it always returns an instance of MbTable if the attribute is found.
I was able to access the complex attributes with the following code in my node definition:
#Override
public void evaluate(MbMessageAssembly inAssembly, MbInputTerminal inTerminal) throws MbException {
checkUserDefinedProperties();
}
/**
* #throws MbException
*/
private void checkUserDefinedProperties() throws MbException {
Object obj = getUserDefinedAttribute("geoLocations");
if (obj instanceof MbTable) {
MbTable table = (MbTable) obj;
int size = table.size();
int i = 0;
table.moveToRow(i);
for (; i < size; i++, table.next()) {
String latitude = (String) table.getValue("latitube");
String longitude = (String) table.getValue("longitude");
}
}
}
The documentation for declaring attributes for user-defined extensions in Java is surprisingly silent on this little bit of detail.
Please note that all the references and code are for WebSphere Message Broker v 8.0.0 and should be relevant for IBM Integration Bus 9.0.0.1 too.

Map cypher query result to domain object

I just started to use Neo4j with Spring Data and I'm not able to recover graph objects and convert them back to domain objects. I must say I have no previous experience in that kind of databases.
In that way, I'm using Spring Data repositories. For standard queries the repository code is auto-generated but I would like to define some custom methods, so I created my custom repository as explained here.
For example, I would like to be able to update a certain property value (currentValue property in this case) from a given edge between two certain nodes (searchByUserName is a previously defined index in my node entity which represents a user). I'm using the query method from the Neo4j template in my custom repository implementation as follows:
public class TwitterUserRepositoryImpl implements TwitterUserRepositoryCustom{
#Autowired
private Neo4jOperations neo4jTemplate;
public void updateRelationshipValueByUserName(
String userAUserName, String userBUserName, double value){
HashedMap params = new HashedMap();
params.put("userAUserName", userAUserName);
params.put("userBUserName", userBUserName);
params.put("value", value);
String query = "START x=node:searchByUserName(userName = {userAUserName}), " +
"y=node:searchByUserName(userName = {userBUserName})" +
" MATCH (x)-[r:FOLLOWS]->(y)" +
" SET r.currentValue = {value}" +
" RETURN r";
Result<Map<String, Object>> relationships = neo4jTemplate.query(query, params);
/* let's try to recover the relationship entity and do some more stuff */
}
The cypher query returns an "edge" between two users, where its relationship type is "FOLLOWS", simulating a Twitter users network. I have no idea how to convert this QueryResult object back to my RelationshipEntity object. Is that possible?
Just use the result-dsl: http://static.springsource.org/spring-data/data-graph/snapshot-site/reference/html/#d5e1118
relationships.to(MyRelationshipEntity.class)
will return you a Result<MyRelationshipEntity> which is an Iterable

Resources