I want to add a Relationship between two already existing nodes in Neo4j - how to do that using Spring Data Neo4jRepository? - neo4j

The cypher query to perform the above task would be as follows:
MATCH (a:Buyer), (b:Seller)
MERGE (a) -[:BUY {quantity: 150}]-> (b);
I want the equivalent Neo4jRepository function or some equivalent code that can serve the same above purpose. Please post the answer if you know some solution.
[Updates]
I have posted an answer below. But I am also expecting some other kind of solutions to this purpose. Please feel free to post alternative solutions as answers.

One Possible Way
If we have two Nodes/Entities as Buyer and Stock as POJO classes in SpringBoot Code, and if we are trying to add a relationship called [:HAS] between two such nodes then we can do the following.
#Node("Stock")
class Stock
{
#Id #GeneratedValue(...)
private Long id;
/* Other attributes here --- */
}
#Node("Buyer")
class Buyer
{
#Id #GeneratedValue(...)
private Long id;
/* Other Attribute Variables Here --- */
#Relationship(type="HAS")
private List<Stock> stockList;
public List<Stock> getStockList()
{
return stockList;
}
public void setStockList(List stockList)
{
this.stockList = stockList;
}
}
So we can do this to create the desired relationship.
buyer.getStockList().add(newStock);
buyerRepo.save(buyer);
Here buyerRepo is the object of a Repository that implements Neo4jRepository<Buyer, Long>

Related

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.

How to make node changes in a TransactionEventHandler that are returned within the same CREATE query

I am trying to implement a plugin for neo4j to add an autoincrement ID using GraphAware library. To this end, I've written the following classes:
public class ModuleBootstrapper implements RuntimeModuleBootstrapper
{
public RuntimeModule bootstrapModule(String moduleId, Map<String, String> config, GraphDatabaseService database)
{
return new MyModule(moduleId, config, database);
}
}
And:
public class MyModule extends BaseTxDrivenModule<Void>
{
int counter = 0;
public Void beforeCommit(ImprovedTransactionData transactionData)
throws DeliberateTransactionRollbackException
{
if (transactionData.mutationsOccurred()) {
for (Node newNode : transactionData.getAllCreatedNodes()) {
newNode.setProperty("id", counter++);
}
}
}
}
And for the testing I can execute:
CREATE (n);
And then:
MATCH (n) RETURN n;
And I can see the effect of my plugin as some id property added to the node. But when I run:
CREATE (n) RETURN n;
The returned node does not have the mentioned id property but again, when I match the node in a separate query, I see the things have worked out just fine. It's just that in the CREATE query, the returned node infos are the ones before my plugin modified them.
The questions are; why is that? Didn't I modify the nodes through my plugin within the transaction? Shouldn't the returned nodes be showing the modifications I've made on them? Is there any way I can make this happen?
While you're still within the transaction, the Cypher result is already computed and there is no clean way to add additional informations to it.
I guess a feature request on the neo4j repository could be cool but in total honesty this would require a serious change into the neo4j core codebase.
BTW, the incremental ID is already implemented in the graphaware-uuid plugin : https://github.com/graphaware/neo4j-uuid#specifying-the-generator-through-configuration

Security Context in terms of QueryDslPredicateExecutor and Spring Data Rest

I'm building REST API on the top of Spring Data Rest. Initially all repositories where extending JpaRepository. Lately decision has been made to take a more flexible approach and use QueryDslPredicateExecutor<T> along with QuerydslBinderCustomizer<Q>.
Pretty much all findAll methods exposed in repositories should address two scenarios
principal has a role ROLE_ADMIN then no filtering should be applied a part from Pageable,Sort
principal does not have a role ROLE_ADMIN I would return only those entities which belong to the current user
Getting that done was as simple as annotating findAll method as below.
#Query("select e from Entity e where e.field = ?#{principal} or 1=?#{hasRole('ROLE_ADMIN') ? 1 : 0}")
Page<Entity> findAll(Pageable pageable);
Now I want our findAll to be something similar to below
Page<Entity> findAll(Predicate predicate, Pageable pageable)
Predicate is being build from request parameters(courtesy of #QuerydslPredicate) and is being passed in to RepositoryEntityController which is all being managed by spring-data-rest which is great.
#ResponseBody
#RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
public Resources<?> getCollectionResource(#QuerydslPredicate RootResourceInformation resourceInformation,
DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler)
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
I want to tweak that predicate(2 scenarios as above that I want to address).
It would be something simialr to below.
BooleanBuilder builder = new BooleanBuilder(predicateBuildFromHttpRequest);
builder.and(predicateAddressingOurRequirements);
builder.getValue();
#PostFilter won't be an option as return type for all repos is Page<Entity>.
Use case that I want to address seems to be quite common to me. Having said that I had a look at spring-data and spring-data-rest documentation and could not find anything related to my question.
Question is : Am I missing something obvious here and there is a quick win for it? or I would need to implement custom solution myself? Any comments very much appreciated!
The Querydsl predicates are constructed by QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver which is sadly package private and can't be directly extended.
However, you can make a copy of that, add your security predicate logic and then drop in your implementation instead of the former resolver.
public class MyQueryDslRootResourceArgumentResolver extends RootResourceInformationHandlerMethodArgumentResolver {
// the most of the code is ommitted, the content is identical with
// QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver,
// the important part is postProcessMethod where you can modify the predicate
#Override
#SuppressWarnings({"unchecked"})
protected RepositoryInvoker postProcess(MethodParameter parameter, RepositoryInvoker invoker,
Class<?> domainType, Map<String, String[]> parameters) {
Object repository = repositories.getRepositoryFor(domainType);
if (!QueryDslPredicateExecutor.class.isInstance(repository)
|| !parameter.hasParameterAnnotation(QuerydslPredicate.class)) {
return invoker;
}
ClassTypeInformation<?> type = ClassTypeInformation.from(domainType);
QuerydslBindings bindings = factory.createBindingsFor(null, type);
// modify your predicate here
Predicate predicate = predicateBuilder.getPredicate(type, toMultiValueMap(parameters), bindings);
return new QuerydslRepositoryInvokerAdapter(invoker, (QueryDslPredicateExecutor<Object>) repository, predicate);
}
}
Then add you own configuration class with the custom resolver implementation.
public class CustomRepositoryRestMvcConfiguration extends RepositoryRestMvcConfiguration {
#Autowired
ApplicationContext applicationContext;
#Override
public RootResourceInformationHandlerMethodArgumentResolver repoRequestArgumentResolver() {
QuerydslBindingsFactory factory = applicationContext.getBean(QuerydslBindingsFactory.class);
QuerydslPredicateBuilder predicateBuilder = new QuerydslPredicateBuilder(defaultConversionService(),
factory.getEntityPathResolver());
return new MyQueryDslRootResourceArgumentResolver(repositories(),
repositoryInvokerFactory(defaultConversionService()), resourceMetadataHandlerMethodArgumentResolver(),
predicateBuilder, factory);
}
}
Here is an example project that modifies the Predicate (that is produced by the parameters from url) before passing it to the repository.
The demonstration of what David Siro explained above
https://github.com/yeldarxman/QueryDslPredicateModifier

AutoFixture constrained string parameter

Is there a simple way to specify a list of possible values for the parameter orderBy? Not one by one please, otherwise I would not be making the question. I want to specify that orderby makes sense only if it is chosen from a predetermined list. Suppose the list is very large...still not random. This cannot be that hard...no single example of such a simple task.
[Test, AutoData]
public override void IndexReturnsView(int? pageIndex, int? pageSize, string orderBy, bool? desc)
{
.....
}
EDIT:
All I want is to read the possible values from a list as I would do with the ValueSource attribute. However, it seems not to work with AutoFixture. If I specified e.g. [ValueSource("GetOrderByColumnNames")] my test does not work anymore. I have no idea of what I am doing wrong. Unfortunately AutoFixture lacks useful documentation and the examples are very basic. Is there a working example of this scenario that I can use to guide myself here?
This has to be a very common situation, however I have been looking for days with no luck :(.
Appreciated!
If I understand the question correctly, the problem is that the orderBy value should be randomly selected from a list of predefined values, but that list might be too large to use with [InlineAutoData].
The easiest way to do this that I can think of is to introduce a helper type. This might actually be a valuable addition to the application code itself, as it makes the role of various values more explicit, but if not, you can always add the wrapper type to the test code base.
Something like this is the minimum you'll need:
public class OrderCriterion
{
public OrderCriterion(string value)
{
Value = value;
}
public string Value { get; }
}
If we also imagine that this class exposes a list of ValidValues, you can implement an AutoFixture Customization using the ElementsBuilder class:
public class OrderCriterionCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
new ElementsBuilder<OrderCriterion>(OrderCriterion.ValidValues));
}
}
Then you create a data source attribute for your test code base:
public class TestConventionsAttribute : AutoDataAttribute
{
public TestConventionsAttribute() : base(
() => new Fixture().Customize(new OrderCriterionCustomization()))
{
}
}
This enables you to write a test like this, which passes:
[Theory, TestConventions]
public void IndexReturnsView(
int? pageIndex,
int? pageSize,
OrderCriterion orderBy,
bool? desc)
{
Assert.Contains(orderBy.Value, OrderCriterion.ValidValues.Select(x => x.Value));
}
Notice that instead of declaring the orderBy parameter as a string, you declare it as an OrderCriterion, which means that AutoFixture will be detect its presence, and the Customization then kicks in.
See also https://stackoverflow.com/a/48903199/126014

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

Resources