Having trouble getting Entity's "child" loaded via #Query in Sprint Neo4j - neo4j

I have an entity like this:
#NodeEntity
public class Move {
#Id #GeneratedValue private Long id;
#Property("placement")
private String placement;
#Relationship(type="PARENT", direction=Relationship.INCOMING)
BoardPosition parent;
#Relationship("CHILD")
BoardPosition child;
[...]
When I "load" it like this:
Move the_move = m_store.findByPlacement("C1");
log.info("Move direct load: " + the_move.toString());
it works fine. Both the parent and child properties have the correct value.
However, when I access it via a query defined like this:
public interface MoveStore extends Neo4jRepository<Move, Long> {
public Move findByPlacement(String placement);
#Query("MATCH (n:Move) RETURN n")
public List<Move> findAll();
and accessed like this:
ListIterator<Move> moves = m_store.findAll().listIterator();
while(moves.hasNext() ) {
log.info(moves.next().toString());
}
it is missing the child value (is null).
This experiment is strange:
while(moves.hasNext() ) {
Move m = moves.next();
log.info(m.toString()); // This is missing m.child's value
// find the same Move, by passing in the id of this one:
m = m_store.findById(m.id).orElse(null);
log.info(m.toString()); // This has the correct value for m.child!
}
What am I missing? How can I make the query load the child property?

When you are using the custom query, you have also to return the relationship and related nodes to get the child populated.
e.g. #Query("MATCH (n:Move)-[rel:CHILD]-(c:BoardPosition) RETURN n, rel, c") would do the job for a 1:1 relationship otherwise a collect(...) is needed to get the list in the same result "row" as the node you are querying for.

Related

Spring Data Neo4j Repository Save method is doing UNWIND MATCH instread of UNWIND CREATE

Please find the below classes for spring data neo4j rest example
Model Class
#Data
#NodeEntity
public class Model implements Serializable {
#Id
#GeneratedValue
private Long id;
private String name;
private String uUID;
private boolean status = true;
#CreatedDate
private Date createdDate;
#LastModifiedDate
private Date modifiedDate;
}
ModelRepository Class
#Repository
public interface ModelRepository extends Neo4jRepository<Model, Long> {
Optional<Model> findByStatusTrueAndUUID(UUID uuid);
Stream<Model> streamAllByStatusTrue();
}
Service Class Method
public Model createModel(Model request) throws DSException {
return modelRepository.save(request);
}
Repository.save method generated the below cypher query
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`Model` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=1, props={createdDate=null, name=1-name, modifiedDate=2019-02-26T12:05:16.184Z, uUID=05fdb066-13a4-4ed2-b53f-f3e48b5ff9ba, status=true}}]}
Because of the above cypher query which have MATCH instead of CREATE, the request node is not persisting in the neo4j database
Please help in understanding and solving the issue.
Below are versions used:
spring-data-neo4j:5.0.7.RELEASE
Problem was with the type of the property added. If you wont pass the right property type like string or integer as provided by the #NodeEntity, we encounter this error
I encountered the same problem.
Finally I found if you assigned the id property for Model the Neo4j Repository Save method is doing UNWIND MATCH instead of UNWIND CREATE;
If you didn't assign the id, then the Neo4j Repository Save method is doing UNWIND CREATE.

How to dynamically change entity types in neo4j-ogm or spring-data-neo4j?

There was a question on "how to add labels dynamically to nodes in Neo4j". Is there a way to dynamically change the entity types?
Take an example:
#NodeEntity
public class User {
#Properties(prefix = "custom")
private Map userProperties;
}
I see from https://neo4j.com/blog/spring-data-neo4j-5-0-release/ that I can create dynamic properties. Can I have dynamic types during run-time as well? I want to change "User" type to "Consumer"/"Admin"/"Producer" dynamically when needed. The entity types are non-exhaustive.
Thanks in advance! :)
There is an #Labels annotation on a Set<String> that is stored/managed in addition to the main type from the class and interfaces.
see: https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#reference:annotating-entities:node-entity:runtime-managed-labels
The #Labels mechanism is great and for many use cases the best solution I'd say.
If you want to have another class back out of your repository, than there's indeed much more work needed.
I'm doing this in a music related project. I have Artist (not abstract and totally usable for anything where I don't know whether it's a band or not) and Band and SoloArtist extending from Artist, with additional labels:
#NodeEntity
public class Artist {}
#NodeEntity
public class Band extends Artist{}
What I do know in a custom repository extension is this:
interface ArtistRepository<T extends Artist> extends Repository<T, Long>, ArtistRepositoryExt {
Optional<T> findOneByName(String name);
// Specifying the relationships is necessary here because the generic queries won't recognize that
// Band has a relationship to country that _should_ be loaded with default depth of 1.
#Query("MATCH (n:Artist) WITH n MATCH p=(n)-[*0..1]-(m) RETURN p ORDER BY n.name")
List<T> findAllOrderedByName();
#Query("MATCH (n:Artist) WHERE id(n) = $id WITH n MATCH p=(n)-[*0..1]-(m) RETURN p")
Optional<T> findById(#Param("id") Long id);
<S extends T> S save(S artist);
}
interface ArtistRepositoryExt {
Band markAsBand(Artist artist);
SoloArtist markAsSoloArtist(Artist artist);
}
class ArtistRepositoryExtImpl implements ArtistRepositoryExt {
private static final String CYPHER_MARK_AS_BAND = String.format(
"MATCH (n) WHERE id(n) = $id\n" +
"OPTIONAL MATCH (n) - [f:BORN_IN] -> (:Country)\n" +
"REMOVE n:%s SET n:%s\n" +
"DELETE f",
SoloArtist.class.getSimpleName(),
Band.class.getSimpleName());
private static final String CYPHER_MARK_AS_SOLO_ARTIST = String.format(
"MATCH (n) WHERE id(n) = $id\n" +
"OPTIONAL MATCH (n) - [f:FOUNDED_IN] -> (:Country)\n" +
"REMOVE n:%s SET n:%s\n" +
"DELETE f",
Band.class.getSimpleName(),
SoloArtist.class.getSimpleName());
private final Session session;
public ArtistRepositoryExtImpl(Session session) {
this.session = session;
}
#Override
public Band markAsBand(Artist artist) {
session.query(CYPHER_MARK_AS_BAND, Map.of("id", artist.getId()));
// Needs to clear the mapping context at this point because this shared session
// will know the node only as class Artist in this transaction otherwise.
session.clear();
return session.load(Band.class, artist.getId());
}
#Override
public SoloArtist markAsSoloArtist(Artist artist) {
session.query(CYPHER_MARK_AS_SOLO_ARTIST, Map.of("id", artist.getId()));
// See above
session.clear();
return session.load(SoloArtist.class, artist.getId());
}
}
While this works neat, I'll get the idea of effort in a deeper nested class scenario. Also, you have do redeclare derived finder methods as I deed if you want to use an repository in a polymorphic way.
I keep dedicated repositories, too.
If this question was still relevant to you, let me know if works for you, too. You'll find the whole project here:
https://github.com/michael-simons/bootiful-music

Neo4j SDN 4 Cypher query based on GraphId and entity type

I have implemented a base class for all entities in my application that can have a subscribers:
#NodeEntity
public abstract class Subscribable extends BaseEntity {
private final static String SUBSCRIBED_TO = "SUBSCRIBED_TO";
#Relationship(type = SUBSCRIBED_TO, direction = Relationship.INCOMING)
private Set<User> subscribers = new HashSet<>();
public Set<User> getSubscribers() {
return subscribers;
}
public void setSubscribers(Set<User> subscribers) {
this.subscribers = subscribers;
}
public boolean addSubscriber(User subscriber) {
return subscribers.add(subscriber);
}
}
Also I have a few concrete types that extend Subscribable class, for example Product, Tag, User etc
Right now in order to get all User subscribed to a particular Subscribable I use a following method with a Cypher query:
#Query("MATCH (sub:Subscribable)<-[:SUBSCRIBED_TO]-(u:User) WHERE id(sub) = {subscribableId} RETURN u")
Set<User> findAllUsersBySubscribableId(#Param("subscribableId") Long subscribableId);
The issue is that AFAIK different objects of different types can potentially have the same Id so looks like the approach to get all User based only on subscribableId(GraphId) can not be consistent and potentially can lead to a different results.
The idea is to supply additional parameter to findAllUsersBySubscribableId method, for example String type in order to specify what exact type I expect under provided subscribableId.. for example "Product"
Please help to extend the following Cypher query in order to support entity type:
MATCH (sub:Subscribable)<-[:SUBSCRIBED_TO]-(u:User) WHERE id(sub) = {subscribableId} RETURN u

Cannot use enum in repository query (neo4j/Spring Data)

I'm having a problem querying based on an Enum property of my NodeEntity.
The NodeEntity in question is defined:
#NodeEntity(label = "Entity")
public class MyEntity {
#GraphId
private Long internalId;
....
private State state;
#Transient
public enum State {
STATEONE, STATETWO, STATETHREE
}
....
It saves without a problem, the state Enum represented perfectly, and I can query using other properties (Strings) with no problem at all. However the problem is the following query in a repository:
#Query("MATCH (entity:Entity {state:{0}})" +
"RETURN entity")
List<MyEntity> findByState(MyEntity.State state)
i.e. find all entities with the given state.
There's no exception, however using this simply returns a List of 0 Entities.
I've tried all kinds of variations on this, using a WHERE clause for example, with no luck.
The Entities are persisted properly, using findAll() in the same test returns the expected List of Entities with their states exactly as I would expect.
Any thoughts?
Not quite sure what the value #Transient adds to the enum. It is anyway not persistable as a node or relationship in Neo4j. It is sufficient to define the field as one that should persist with
private State state;
and leave off the #Transient annotation from the enum.
With it, SDN ignores the field sent to the derived query.
However, if you have a good reason to mark the enum #Transient, please do share it and we'll re-visit this case.
There is a general problems using spring data rest interface to search on enum fields. Just using the enum-to-string converter cannot work for search where you want to find if the value is IN a collection of values:
public interface AppointmentRepository extends Neo4jRepository<Appointment, Long> {
Page<Appointment> findByDayOfWeekIn(#Param("days") List<DayOfWeek> days, Pageable pageable);
}
The above does not work out of the box because neo4j will try to convert a List to your property type: DayOfWeek
In order to work around this I needed a custom converter that handles both requests providing collection of values (the search) and single values (the normal read and write entity):
#SuppressWarnings({ "unchecked", "rawtypes" })
public abstract class SearchQueryEnumConverter<T extends Enum> {
private Class<T> enumType;
public SearchQueryEnumConverter() {
enumType = (Class<T>) ((ParameterizedType) this.getClass()).getActualTypeArguments();
}
public Object toGraphProperty(Object value) {
if (Collection.class.isAssignableFrom(value.getClass())) {
List<T> values = (List<T>) value;
return values.stream().map(Enum::name).collect(Collectors.toList());
}
return ((Enum) value).name();
}
public Object toEntityAttribute(Object value) {
if (Collection.class.isAssignableFrom(value.getClass())) {
List<String> values = (List<String>) value;
return values.stream().map(v -> (T) T.valueOf(enumType, v)).collect(Collectors.toList());
}
return (T) T.valueOf(enumType, value.toString());
}
}
The abstract converter can be reified by all enums, and used as parameter of the #Convert annotation:
public enum EnumType {
VALUE_A, VALUE_B;
public static class Converter extends SearchQueryEnumConverter<EnumType> implements AttributeConverter {
}
}
#NodeEntity
public Entity {
#Property
#Convert(EnumType.Converter.class)
EnumType type;
}

Return self referencing nodes as a parent-child using neo4J OGM and cypher

In the Neo4J database, I have an entity 'Person' which can be related to other Persons as a PARENT_OF or a CHILD_OF relationship
For instance, the cypher query
Match (p:Person) - [:PARENT_OF] -> (c:Person) where id(p) = {p_id} return p, c
will return a parent record p with id p_id and a set of child records c
In Java, I have a base class Person
public class Person {
#GraphId private Long id;
#Property #NonNull private String profile;
#Relationship(type="RELATED_TO", direction="OUTGOING") Experience experience;
#Relationship(type="RELATED_TO", direction="OUTGOING") Activity activity;
}
and 2 child classes
#NodeEntity (label="Person")
public class Parent extends Person {
#Relationship(type="PARENT_OF", direction="OUTGOING") private List<Child> child;
}
#NodeEntity (label="Person")
public class Parent extends Person {
#Relationship(type="PARENT_OF", direction="OUTGOING") private List<Child> children;
}
Assume suitable getters and setters
I want to use Neo4J-OGM in Java and get a Parent given a parent's id. This parent should have a list of Children pre-loaded
So a query case like this:
final Iterable<Parent> parents = session.query(Parent.class, "MATCH (parent:Person) - [:PARENT_OF] -> (child:Person) where id(parent) = {personId} return parent --> child", ImmutableMap.of("personId", 15L));
System.out.println (parent.get(0));
I want the parent object to be loaded with all its children (the List attribute should be populated).
How do I go about doing this? I need to use Cypher.
The simplest thing to do is
Person parent = session.load(Person.class, parentId);
The default loading depth is one so it will load children. You can customize this depth if you wish.
Not sure why you need to use Cypher but if you want to, then the loadAll methods on the Session which accept Filter should work.
Here's an example https://github.com/neo4j/neo4j-ogm/blob/master/src/test/java/org/neo4j/ogm/domain/tree/Entity.java
used in test https://github.com/neo4j/neo4j-ogm/blob/master/src/test/java/org/neo4j/ogm/integration/tree/TreeIntegrationTest.java

Resources