Map cypher query result to domain object - neo4j

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

Related

How do we generate a cypher query by using Java and APOC for Neo4j?

I am trying to create my own procedure in Java in order to use it for Neo4j.I wanted to know how we can execute Cypher code in Java ?
I tried to use graphDB.execute() function but it doesn't work.
I just want to execute a basic code in Java by using Neo4j libraries.
Example of a basic code I want to execute:
[EDIT]
public class Test
{
#Context public GraphDatabaseService graphDb;
#UserFunction
public Result test() {
Result result = graphDb.execute("MATCH (n:Actor)\n" +
"RETURN n.name AS name\n" +
"UNION ALL MATCH (n:Movie)\n" +
"RETURN n.title AS name", new HashMap<String, Object>());
return result;
}
}
If you want to display nodes (as in the graphical result view in the browser), then you have to return the nodes themselves (and/or relationships and/or paths), not the properties alone (names and titles). You'll also need this to be a procedure, not a function. Procedures can yield streams of nodes, functions can only return single values.
Change this to a procedure, and change your return type to be something like Stream<NodeResult> where NodeResult is a POJO that has a public Node field.
You'll need to change your return accordingly.

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/

How can I find the Neo4J Index Key's for Indexes created on nodes using Spring Data?

I have created nodes using Spring using the following basic process (see below). I have a POJO for my Concepts and using this object and a Neo4J template to create nodes with indexes. I am still unable to discover what the 'KEY' for the created index is. I know the name of the index is 'CID' but assumed the 'KEY' would be 'conceptId'. However, when I use the following query (see below), no data is returned. It is confirmed that the Index does exist, but I am unable to find out what the proper 'KEY' for said index is so I can utilize it to improve query performance. I am able to query a specified node using WHERE clause searching for a specific value for a property of said node. However, when I try to find the node using the Index 'CID' with Key 'conceptId' no nodes are returned.
// Concept POJO
#NodeEntity
public class Concept {
#GraphId
private Long nodeId;
#Indexed(indexName="CID", fieldName="conceptId")
private Long conceptId;
...
// service where code to create Concept Nodes exists
#Repository
public class ConceptService {
#Autowired
private Neo4jTemplate n4jTemplate;
#Autowired
private ConceptRepository cr;
// Call to create node in a 'service'
public void addConceptNode(Concept concept) {
concept = n4jTemplate.save(concept);
}
...
//Cypher Queries used to retrieve nodes using index
START a=node:CID( conceptId = "66573009")
RETURN a;
// this returns 0 nodes quickly
START a=node:CID( conceptid = "66573009")
RETURN a;
// this returns 0 nodes quickly
START a=node:CID( CID = "66573009")
RETURN a;
// this returns 0 nodes quickly
START a=node:CID( cid = "66573009")
RETURN a;
// this returns 0 nodes quickly
START a=node:CID( CONCEPTID = "66573009")
RETURN a;
// this returns 0 nodes quickly
// Cypher query not using index to retrieve same node
START a=node(*)
WHERE HAS(a.conceptId) AND a.conceptId = 66573009
RETURN a;
// this returns 1 node in 77365ms
//'quickly' = approx.(43-87ms).
There is more to the code than what is shown, but this gives you the basic gist of how I am creating nodes with indexes in a Neo4J DB. There are more properties and more indexes. When using Spring to retrieve the nodes it seems to 'auto' use (I am assuming it is using the index) the index created because it returns the results faster than using the Neo4J data browser.
Any help would be greatly appreciated.
Thanks!

MvcSiteMapProvider ISiteMapBuilder in conjunction with IDynamicNodeProvider

I'm using MvcSiteMapProvider 4.4.3 to dynamically build a sitemap from the database. I'm following this article: https://github.com/maartenba/MvcSiteMapProvider/wiki/Multiple-Sitemaps-in-One-Application because I'm using multiple sitemaps.
This works and this is the basic structure which is returned:
Home
News
Products
About
Contact
One of the nodes (/Products) should be dynamically populated again based on different data. So for this I need a IDynamicNodeProvider implementation on the /Productsnode? (please correct me if i'm wrong?)
Anyway, I think I do need the above. Documentation shows ways to do this on a node defined in XML and on a node defined using attributes on controller actions, but not 'manually' in a ISiteMapBuilder. So if I set the .DynamicNodeProvider property of the ISiteMapNode instance it doesn't seem to get instantiated... The .HasDynamicNodeProvider property also returns false.
Looking at the source, i see PluginProvider-stuff which is related to DynamicNodeProviderStrategy and there you go, they've lost me...
How do I create a ISiteMapNode for "/Products" in my ISiteMapBuilder so that it's descendents (/Products/Cat and /Products/Cat/Product) are dynamically loaded from the database?
You can do this with ISiteMapBuilder, but you are probably better off instead implementing ISiteMapNodeProvider. The reason is because adding the nodes to the SiteMap must be done at the very end of the process after all nodes have been instantiated to ensure that every node is correctly mapped to a parent node (except of course, the root node which doesn't need a parent). This was the major design change that was done in 4.3.0.
The default SiteMapBuilder class is now already set up to ensure
The nodes are properly mapped to their parent nodes
There is only 1 root node
All nodes are added to the SiteMap
The visitors are executed last after the SiteMap is completely built
It dosen't make sense to add more than one ISiteMapBuilder instance because this makes it possible to circumvent this important logic. Therefore, it is best if you do not implement ISiteMapBuilder, but instead implement ISiteMapNodeProvider.
The SiteMapBuilder class takes an ISiteMapNodeProvider as a dependency through its constructor. You can use the CompositeSiteMapNodeProvider class to handle multiplicity on this interface so you can add more than one ISiteMapNodeProvider implementation, if needed.
The ISiteMapNodeProvider interface looks like this:
public interface ISiteMapNodeProvider
{
IEnumerable<ISiteMapNodeToParentRelation> GetSiteMapNodes(ISiteMapNodeHelper helper);
}
There is just 1 method to implement. In addition, many of the common (but optional) services are injected through the interface automatically from the SiteMapBuilder class through ISiteMapNodeHelper.
This class is at a lower level than IDynamicNodeProvider. You are interacting with ISiteMapNode directly, but all interaction with the SiteMap class is handled by SiteMapBuilder. The ISiteMapNode is wrapped in a ISiteMapNodeToParentRelation instance, which is just there to ensure that its parent node key can be tracked until the time it is added to the SiteMap object.
Your SiteMapNodeProvider should look something like this:
public class CustomSiteMapNodeProvider
: ISiteMapNodeProvider
{
private readonly string sourceName = "CustomSiteMapNodeProvider";
#region ISiteMapNodeProvider Members
public IEnumerable<ISiteMapNodeToParentRelation> GetSiteMapNodes(ISiteMapNodeHelper helper)
{
var result = new List<ISiteMapNodeToParentRelation>();
using (var db = new DatabaseContextClass())
{
foreach (var category in db.Categories.ToList())
{
var categoryRelation = this.GetCategoryRelation("Products", category, helper);
result.Add(categoryRelation);
}
foreach (var product in db.Products.Include("Category"))
{
var productRelation = this.GetProductRelation("Category_" + product.CategoryId, product, helper);
result.Add(productRelation);
}
}
return result;
}
#endregion
protected virtual ISiteMapNodeToParentRelation GetCategoryRelation(string parentKey, Category category, ISiteMapNodeHelper helper)
{
string key = "Category_" + category.Id;
var result = helper.CreateNode(key, parentKey, this.sourceName);
var node = result.Node;
node.Title = category.Name;
// Populate other node properties here
// Important - always set up your routes (including any custom params)
node.Area = "MyArea"; // Required - set to empty string if not using areas
node.Controller = "Category"; // Required
node.Action = "Index"; // Required
node.RouteValues.Add("id", category.Id.ToString());
return result;
}
protected virtual ISiteMapNodeToParentRelation GetProductRelation(string parentKey, Product product, ISiteMapNodeHelper helper)
{
string key = "Product_" + product.Id;
var result = helper.CreateNode(key, parentKey, this.sourceName);
var node = result.Node;
node.Title = product.Name;
// Populate other node properties here
// Important - always set up your routes (including any custom params)
node.Area = "MyArea"; // Required - set to empty string if not using areas
node.Controller = "Product"; // Required
node.Action = "Index"; // Required
node.RouteValues.Add("id", product.Id.ToString());
node.RouteValues.Add("categoryId", product.CategoryId.ToString()); // Optional - use if you have a many-to-many relationship.
return result;
}
}
The above example assumes that you have added a node by other means that has the key set to "Products", which all categories will be children of. You can of course adjust this to meet your needs.
It is typically best if you only implement this interface 1 time and use a single database connection to load the entire SiteMap. You can always refactor this into multiple classes that each handle a single table on your side of the interface to separate concerns. But it is generally best if you put all of the key mapping logic between related entities together to make it easier to maintain.
For additional examples of implementations of this interface, see XmlSiteMapNodeProvider and ReflectionSiteMapNodeProvider.

breezejs entity query with select returning undefined

With the code below I am having an issue where not all the columns are return data in the data.results array. For example if col4 is null in the database for row 1 then data.results[0] does not contain an element for col4, but row 2 has a value then data.results[1] will contain the value for col4. I would like each return item in the array to contain all items with the database value or null. If null can't be returned then an empty string would do.
var query = new breeze.EntityQuery()
.from('mytable')
.where('col1', 'substringof', '2')
.select('col1,col2,col3,col4')
.orderBy('col1')
.take(200);
return _manager
.executeQuery(query)
.then(function (data) {
return data.results;
})
.fail(queryFailed);
}
By default breeze does not serialize null values in its JSON results. This was a deliberate choice to reduce the breeze payload over the wire. And.. this is not typically an issue with queries that return "entities". i.e. data for which breeze metadata exists. Because the properties are already defined on such entities.
But if you are returning an anonymous result as you are, then this can be a problem. You can obviously work around it because you know the properties that you are requesting and can update them after the query if they are not in the result.
But you can also change breeze's default configuration to accommodate this via the "BreezeConfig" class.
BreezeConfig enables customization of components supporting Breeze-related operations in the Web API. BreezeConfig defines default behaviors; you can substitute your own behaviors by deriving from it and overriding its virtual methods. Breeze.NET will discover your subclass among the assemblies referenced by your project and use it instead of BreezeConfig.
To use BreezeConfig to configure the Json.Net serializer with specific settings. You can replace those settings by writing a subclass of BreezeConfig that overrides the 'CreateJsonSerializerSettings' method as shown in this example:
public class CustomBreezeConfig : Breeze.WebApi.BreezeConfig {
protected override JsonSerializerSettings CreateJsonSerializerSettings() {
var baseSettings = base.CreateJsonSerializerSettings();
baseSettings.NullValueHandling = NullValueHandling.Include;
return baseSettings;
}
Hope this helps.

Resources