How to set depth to 1 in Neo4j Spring Data - spring

How can I set the depth/limit the number of hops in Spring Data?
I'm using Neo4j DB with SDN, and since I have a cycle I keep getting an infinite loop.
I'm trying to retrieve all my nodes with the related relationships and target nodes with a depth of 1 (only one hop) with the following query:
MATCH p = (u1:User)-[r]->(u2:User) WITH *, relationships(p) AS r RETURN u1, collect(r), collect(u2)
My model is as follow:
#Node
public class User {
#Id
#GeneratedValue
private Long id;
#Property
private String name;
#Relationship(type="RELATED")
private List<Relationship> relationship;
//constructor, getter and setters
}
#RelationshipProperties
public class Relationship{
#Id
#GeneratedValue
private Long id;
#Property
private String role;
#TargetNode
private User user;
//constructor, getter and setters
}
I understood that to set the depth to 1 I should add *1 to the relation as below:
MATCH (u1:User})-[r*1]->(u2:User) RETURN u1, collect(r), collect(u2)
Neo4J flag that solution (adding *n) as deprecated an I should operate with a path, so I wrote the following query:
MATCH p = (u1:User)-[r]->(u2:User) WITH *, relationships(p) AS r RETURN u1, collect(r), collect(u2)
The previous query seems to work as intended if I try to execute it in Neo4J Browser but if executed in Spring Data I get an infinite loop
This is the query in my repository
#Query("MATCH p = (u1:User)-[r]->(u2:User) WITH *, relationships(p) AS r RETURN u1, collect(r), collect(u2)")
Collection<User> getAllUser();
This are the dependencies I'm working with:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-bolt-driver</artifactId>
<version>3.2.28</version>
</dependency>

Your assumption regarding the query is wrong.
I understood that to set the depth to 1 I should add *1 to the relation as below:
MATCH (u1:User})-[r*1]->(u2:User) RETURN u1, collect(r), collect(u2)
For MATCH (n)-[r]->(m) the r is exactly one relationship (like your query with *1. Downside of this query style is that every n (in your case the User) has to fulfill this pattern. If there is a User without a relationship, it won't get returned.
Because start and end of your relationship have the same label (User), you could use MATCH p = (u1:User)-[*0..1]->(:User) RETURN u1, collect(relationships(p)), collect(nodes(p))
Another approach, if you have multiple use cases demanding this reduced view, is to use projection:
interface UserWithDepthOne { // *
String getName();
List<RelationshipProjection> getRelationship();
interface RelationshipProjection {
String getRoles();
UserWithoutRelationship getUser();
interface UserWithoutRelationship {
String getName();
}
}
}
(*The projections don't have to be nested, here just for the overview)
And in your repository:
Collection<UserWithDepthOne> getAllUser();
More on projections can be found here.

Related

Spring Boot JPA find, filter

As Spring jpa Provides some usefull features to find Items from a repository by defining it in the method name. e .x findByTitle(String title) then Spring is automatically searching the Title Colum for the given String. If i have an int column named numberOfCopies and i want only to find the datasets with >0 greater then null how would define such a method ?
to filter out those books with the numberOfCopies equals 0 = zero
#Entity
public class Book {
#Id
private int id;
private String title;
private int numberOfCopies;
}
can i use the Repomethod
public List findBooksByNumberOfCopies.greater then 0 ?To Use this Spring Feature without some if or for loops
First, you should use Integer, since it is better, in my opinion, to use wrapper classes than to primitives, and enforce not null requirement through annotations, e.g. #Column(nullable = false)
#Entity
public class Book {
#Id
private Integer id;
private String title;
private Integer numberOfCopies;
}
Then you can add the following two methods in your BookRepository;
List<Book> findByNumberOfCopiesGreaterThan(Integer numberOfCopies);
default List<Book> findAllAvailableBooks() {
return findByNumberOfCopiesGreaterThan(0);
}
and use the default findAllAvailableBooks method, with hardcoded 0 value which is your requirement.
you can easily use
List<Book> findByNumberOfCopiesGreaterThanEqual(int numberOfCopies);
Pretty sure this would work:
public interface BookRepo extends JpaRepository<Book, Integer> {
#Query("SELECT b FROM Book b WHERE b.numberOfCopies >= 0")
public Optional<List<Book>> getTheBooksWithMultCopies();
}
// back in your component class:
{
...
Optional<List<Book>> optionalBookList = myBookRepo.getTheBooksWithMultCopies();
if (optionalBookList.isPresent()){
List<Book> bookList = optionalBookList.get();
}
}
Note that the language within the query is called HQL, which is what is used by Hibernate internally (which is used by JPA internally). It's really not very intimidating - just, know that you the objects in your POJO, which map to your database table, rather than your database table directly.
Also, I'd recommend using Integer over int in entity classes, at least if your value is nullable. Otherwise, numberOfCopies will always default to 0, which may not be desirable and may cause exceptions that are difficult to decipher.
GreaterThanEqual takes an Integer not int
List<Book> findByNumberOfCopiesGreaterThanEqual(Integer numberOfCopies);

Spring-boot jpa how to find entity with max value

Lets tell I have two tables.
CREATE TABLE user (ID int AUTO_INCREMENT,PRIMARY KEY (ID));
CREATE TABLE points (ID int AUTO_INCREMENT, user_id int, points int,PRIMARY KEY (ID));
How can I use spring-boot jpa to request user and max points like this?
select u.ID,max(p.points) from user u, points p where u.id=p.user_id
Or any alternatives to solve this kind of problems?
Assuming you have a Repository of User:
public class User {
private int id;
private List<Point> points;
...
}
With a relationship to the Points object:
public class Point {
private int id;
private User User;
private int points;
...
}
I haven't tested, but you should be able to do:
User findFirstByIdOrderByPointPointsDesc(int userId)
Similar to example 18 in the docs.
The only problem you have, regardless of the query or Spring Data, is if you have two users with the same point values. If you need more logic around tie-breaking, it might be more worth it to write a #Query (with your query, plus the extra tie-breaking logic) or a #NativeQuery.
I usually create a class to hold result such as
public class Result {
private User user;
private int votes;
// getters and setters
}
And write a custom query in the repository to fetch the data
#Query(value = "SELECT new com.package.Result (u, MAX (p.points) )
FROM user u
JOIN points p
ON u.id = p.user_id
GROUP BY u")
List<Result> getPointsPerUser();
Replace com.package.Result with appropriate path to the Result class.
Below method can be written in Repo and used as Transaction as in dao layer, which will be accessible from service layer.
#Query(value = "SELECT max(transactionId) FROM TransactionPayloadInfo")
int getMaxTransactionId();
create a model of data.
public class Data {
private int id;
private int maxPoints;
// getters and setters method
}
And write your query like this for getting model of Data.
#Query(select packagename.Data(u.ID,max(p.points) ) from user u, points p where u.id=p.user_id)
Data findUserWithMaxVots();

Spring Data JPA Projection with select distinct

I have a database table which holds Metadata for documents. My task now is to get a list with documenttypes. The documenttypes are not unique in the database table but of course I want them to be in my list. The sql is very simple:
SELECT DISTINCT groupname, group_displayorder
FROM t_doc_metadata
ORDER BY group_displayorder;
I have learned that I can use projections to get a subset of fields from my entity DocMetadata. I solved this as follows. My Entity:
#Entity
#Table(name="T_DOC_METADATA")
#Data
public class DocMetadata {
..............
#Column(nullable=false)
private String displayname;
#Column(nullable=false)
private Integer displayorder;
#Column(nullable=false)
private String groupname;
#Column(name="GROUP_DISPLAYORDER",
nullable=false)
private Integer groupDisplayorder;
#Column(name="METADATA_CHANGED_TS",
nullable=false,
columnDefinition="char")
private String metadataChangedTimestamp;
..........
}
My inteface for projection:
public interface GroupnameAndOrder {
String getGroupname();
Integer getGroupDisplayorder();
void setGroupname(String name);
void setGroupDisplayorder(int order);
}
Now I thought I'd be extraordinary clever by adding these lines to my repository:
#Query("select distinct d.groupname, d.groupDisplayorder from DocMetadata d order by d.groupDisplayorder")
public List<GroupnameAndOrder> findSortedGroupnames();
Sadly, when iterating over the result list and calling getGroupname() the result is null.
So I changed the lines in my repository according to the documentation:
public List<GroupnameAndOrder> findBy();
Now I get the groupnames but of course they are not unique now. So it doesn't solve my problem.
Is there any way to receive a ordered list with unique groupnames?
You are trying to be too clever. Instead just write the proper find method and return the GroupnameAndOrder. Spring Data JPA will then only retrieve what is needed for the projection.
Something like this should do the trick.
List<GroupnameAndOrder> findDistinctByOrderByGroupDisplayorder();

How to retrieve a path with Spring Data Neo4j 5 repository

I have modelled a simple finite state machine graph in neo4j, where the domain consists of State entities and a FOLLOWED_BY relationship (in cypher: (s1:State)-[r:FOLLOWS]->(s2:State)).
Each has some properties. I need now to analyse relations among the states and don't know how the return type in
the repository interface should look like.
The (shortened) code for the entity and relationship classes (with lombok annotations):
#NodeEntity
#NoArgsConstructor
public class State {
#Getter
private String name;
#Getter
private String inputVariable;
#Getter
private String outputVariable;
}
#RelationshipEntity(type = "FOLLOWED_BY")
#NoArgsConstructor
public class Transition implements FlowTransition {
#Getter
#Property
private String guard;
#Getter
#StartNode
private State sourceState;
#Getter
#EndNode
private State targetState;
}
For some analysis which paths exists from an state to following states where the output variable of first state is used as
the input variable of the following state, I need the path returned from the query. As I'm using SDN I would like to have it
returned in a (custom) query from the repository.
#Repository
public interface StateRepository extends Neo4jRepository<State, Long> {
#Query("MATCH p=allShortestpaths((s1:State)-[r:FOLLOWED_BY*1..200]->(s2:State))"
+ " WHERE s1.outputVariable = s2.inputVariable AND id(s1) = {eId}"
+ " RETURN p)"
??? findAllByBpelPathRegEx(#Param("eId") String startId);
}
My question is: what class should I use as the return type to get the path object? EntityPath or EndResult doesn't seem to exists anymore in SDN5(maybe 4 also), so what to take? Maybe projections, but should they look like?
From this question and answers How do I query for paths in spring data neo4j 4?
:
EntityPath isn't supported since SDN 4 and you should use Iterable<Map<String, Object>> as return type (btw: List<Map<String, Object>> works either). The keys of the Map<String, Object> are the names of variables which you return in your Cypher query (in the example it's p from RETURN p).
BTW: It's maybe better you return RETURN nodes(p) AS nodes, relationships(p) (map-keys: nodes, relationships(p)) as this would return your defined #NodeEntity and #RelationshipEntity objects and not just the simple path objects (which contains just ids (as strings) and not the node objects themselves)
You can take the result in an object class or you need to create a class having #QueryResult annotation collect the s1 and s2.

Spring data JPA Specifications - #OneToMany dependency

i have a problem with getting List from entity Person using Spring data JPA specifications (because of pagination). I need to get all notes by person but dependency between these two entities is on Person side. I don't know how to create my Predicate because Note doesn't contain any attribute related to Person.
I simply can get List with Persons getter but i can't use this way because i need returned data paginated.
#Entity
public class Person implements Serializable {
#Id
private Long personId;
#OneToMany
#JoinColumn(name = "personId")
private List<Note> notes;
}
#Entity
public class Note implements Serializable {
#Id
private Long noteId;
}
Normally, I would write something like this, but i don't have an attribute person in Note and database can't be remapped at this stage.
public static Specification<Note> notesByPerson(final Long personId) {
return new Specification<Note>() {
#Override
public Predicate toPredicate(final Root<Note> root, final CriteriaQuery<?> query,
final CriteriaBuilder builder) {
final Path<Person> per = root.<Person> get("person");
return builder.equal(per.<Long> get("personId"), personId);
}
};
}
Thank you,
Zdend
Solved..
public static Specification<Note> notesByPerson(final Long personId) {
return new Specification<Note>() {
#Override
public Predicate toPredicate(final Root<Note> noteRoot, final CriteriaQuery<?> query,
final CriteriaBuilder cb) {
final Subquery<Long> personQuery = query.subquery(Long.class);
final Root<Person> person = personQuery.from(Person.class);
final Join<Person, Note> notes = person.join("notes");
personQuery.select(notes.<Long> get("noteId"));
personQuery.where(cb.equal(person.<Long> get("personId"), personId));
return cb.in(noteRoot.get("noteId")).value(personQuery);
}
};
}
I am not sure how to do that with Predicates, as I usually dont use them, but in JPQL (or HQL, which is similar), you can do something like this:
SELECT Note n FROM Person.notes WHERE XXXX
It is basically the same thing as doing this in SQL
SELECT n.noteId FROM person as p JOIN persons_notes pn ON pn.person=p.personId JOIN notes as n ON n.noteId=pn.noteId
I would venture a guess that the Predicate method has similar abilities as described above.

Resources