Query by complex Object field - elasticsearch

Consider the document with complex object field:
#Getter #Setter
#Document
class A{
#Id
private String id;
#Field(
type = FieldType.Object,
includeInParent = true
)
private B b;
}
#Getter #Setter
class B{
String field1;
C c;
}
#Getter #Setter
class C{
String field1;
}
public interface ARepo extends ElasticsearchRepository<A, String> {
Optional<A> findByB(B b);
}
When I execute the aRepo.findByB(someB), the built query doesn't extract B fields, but contains the result of someB.toString() as filter for field b.
I have JPA background and expect similar behavior . If this is not supported, what is the recommended approach for these kind of queries ?
Thanks

What you are looking is the equivalent of Query By Example (QBE), this is not yet supported in Spring-Data-ES.
Searching on an class object should be dependend on your mapping, if your mapping is determined by application (#Field on fields with #Mapping and dynamic mapping set to false), and by looking at public interface ElasticsearchRepository<...> - you will have to manually build your query using QueryBuilder (Elastic abstraction) or Criteria (Spring-Data-ES abstraction) and then customizing your repository.
If your mapping is not determined by application (already set on elastic before), you would have to know it field types (is it a 'text', 'keyword', etc), then there might be different behavior expect for each type (match query, term query, filter, etc) - and this will also have to be custom-implemented with QueryBuilder or Criteria.

Related

Spring: combine JPA Derived query methods and query by example

Is it possible to use JPA derived methods and query by example at the same time?
Let's imagine i have two entities like this:
#Entity
#Data
public class Person {
#Id
#GeneratedValue
Long id
String name;
String surname;
#OneToMany
List<Dog> dogs;
}
#Entity
#Data
public class Dog{
#Id
#GeneratedValue
Long id
String name;
}
I'd like to be able to do something like this (just an example):
Person p = new Person ();
p.setName("Mario");
personRepository.findDistinctByDogsIsNotNull(Example.of(p));
The Example.of(p) only works if i do findAll, but it doesn't work if i define inside the repository a method like this
private interface PersonRepository extends JpaRepository<Person, Long>{
List<Person> findDistinctByDogsIsNotNull(Example<Person> example)
}
The error it gives me is something like this:
Failed to create query for method public abstract java.util.List dev.cele.test.repository.PersonRepository.findDistinctByDogIsNotNull(org.springframework.data.domain.Example)! At least 1 parameter(s) provided but only 0 parameter(s) present in query.
So my question is: is it possible to do a query by example in a JPA derived query method?
And if it's not possible how can i create some sort of parametrizable query that also has a predetermined condition?

Elastic search with Spring Data for Reactive Repository - Deleting based on nested attributes

I am using Spring data for Elastic Search and am using the ReactiveCrudRepository for stuff like finding and deleting. I noticed that with attributes that are in root and are simple objects, the deletion works (deleteByAttributeName). However if I have nested objects then it does not work.
Here's my entities
Book
#Data
#TypeAlias("book")
#Document(indexName = "book")
public class EsBook{
#Field(type = FieldType.Long)
private Long id;
#Field(type = FieldType.Nested)
private EsStats stats;
#Field(type = FieldType.Date, format = DateFormat.date)
private LocalDate publishDate;
}
Stats
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class EsStats{
#Field(type = FieldType.Double)
private Double averageRating;
#Field(type = FieldType.Integer)
private Double totalRatings;
#Field(type = FieldType.Keyword)
private String category; //this can be null
}
Here is what I have tried and is working and not working
I used ReactiveCrudRepository to delete documents in index. For all the regular fields on Book Level like id or with id and publishDate deletion works perfectly. As soon as I use embedded object like Stats, it stops working. I see the documents and the stats that I am sending match atleast visually but never finds or deletes them.
I tried to use EqualsAndHashcode in the Stats assuming maybe iternally somehow does not consider equal for some reason. I also tried changing double data type to int, because on looking at the elastic search document, I see that average review if whole number like 3 is save as 3 but when we send it from Java, i see in the debug 3 being shown as 3.0, so I was doubting if that is the case, but does not seem so. Even changing the datatype to int deletion does not work.
public interface ReactiveBookRepository extends ReactiveCrudRepository<EsBook, String> {
Mono<Void> deleteById(long id); //working
Mono<Void> deleteByIdAndPublishDate(long id, LocalDate publishDate); //Nor working
Mono<Void> deleteByIdAndStats(long id, LocalDate startDate);
}
Any help will be appreciated
Have you verified that your Elasticsearch index mapping matches your Spring Data annotations?
Verify that the index mapping defines the stats field as a nested field type.
If not, then try changing your Spring annotation to:
#Field(type = FieldType.Object)
private EsStats stats;

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 jpa projection nested bean

is it possible to have a projection with nested collection with Spring JPA?
I have the following 2 simple entity (to explain the problem)
#Entity
#Table(name = "person")
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList = new ArrayList<>();
}
#Entity
#Table(name = "address")
public class Address implements Serializable {
private Integer id;
private String city;
private String street;
}
Is it possible to have a projection of Person with following attributes filled in ? {person.name, address.city}
I might be wrong in semantics of word Projection. but the problem is what i need to achieve. Maybe it is not possible with Projection, but is there another way to achieve the end goal? Named Entity graph perhaps ?
P.S. please suggest a solution for Spring JPA not Spring Jpa REST
thanks in advance
You're right, Entity Graphs serve this exact purpose - control field loading.
Create entity graphs dynamically from the code or annotate target entities with Named Entity Graphs and then just use their name.
Here is how to modify your Person class to use Named Entity Graphs:
#Entity
#Table(name = "person")
#NamedEntityGraph(name = "persion.name.with.city",
attributeNodes = #NamedAttributeNode(value = "addressList", subgraph = "addresses.city"),
subgraphs = #NamedSubgraph(name = "addresses.city", attributeNodes = #NamedAttributeNode("city")))
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList;
}
And then when loading your person:
EntityGraph graph = em.getEntityGraph("person.name.with.city");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
return em.find(Person.class, personId, hints);
The same applies for queries, not only em.find method.
Look this tutorial for more details.
I think that that's not usual scenario of Data JPA usage. But you can achieve your goal with pure JPQL:
SELECT a.street, a.person.name FROM Address a WHERE …
This solution has 2 drawbacks:
It forces you to have bidirectional relationship Address ←→ Person
It returns List
Another solution (and that's preferred JPA way) is to create DTO like this:
class MyPersonDTO {
private String personName;
private List<String> cities;
public MyPersonDTO(String personName, List<Address> adresses) {
this.personName = personName;
cities = adresses
.stream()
.map(Address::getCity)
.collect(Collectors.toList());
}
}
And the execute JPQL query like this:
SELECT NEW package.MyPersonDTO(p.name, p.addressList) FROM Person p WHERE …
Return type will be List<MyPersonDTO> in that case.
Of course you can use any of this solutions inside #Query annotation and it should work.

How to use projection interfaces with pagination in Spring Data JPA?

I'm trying to get a page of a partial entity (NetworkSimple) using the new feature of spring data, projections
I've checked the documentation and if I request only:
Collection<NetworkSimple> findAllProjectedBy();
It works, but if I'm using pageable:
Page<NetworkSimple> findAllProjectedBy(Pageable pageable);
It throws an error:
org.hibernate.jpa.criteria.expression.function.AggregationFunction$COUNT cannot be cast to org.hibernate.jpa.criteria.expression.CompoundSelectionImpl
Any one has already work with this ?
My NetworkSimple class is the following:
public interface NetworkSimple {
Long getId();
String getNetworkName();
Boolean getIsActive();
}
Note: This feature should work in the way described by the original poster but due to this bug it didn't. The bug has been fixed for the Hopper SR2 release, if you're stuck on an earlier version then the workaround below will work.
It is possible to use Pageable with the new query projection features introduced in Spring Data JPA 1.10 (Hopper). You will need to use the #Query annotation and manually write a query for the fields you require, they must also be aliased using AS to allow Spring Data to figure out how to project the results. There is a good example in spring-boot-samples part of the spring boot repository.
In your example it would be quite simple:
#Query("SELECT n.id AS id, n.name AS networkName, n.active AS isActive FROM Network n")
Page<NetworkSimple> findAllProjectedBy(Pageable pageable);
I have made the assumption that your entity looks something like this:
#Entity
public class Network
{
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#Column
private boolean active;
...
}
Spring Data will derive a count query automatically for the paging information. It is also possible to make use of joins in the query to fetch associations and then summarise them in the projection.
I think you need create findAllProjectedBy() as specification.Then you can use findAll() method like this.
example :findAll(findAllProjectedBy(),pageable)
Following link may be help to find how to create specification in spring.
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
The issue may come from the method name. The by keyword means that you ae filterig data by a specific property: findByName for example. Its called query creation from method name:
http://docs.spring.io/spring-data/jpa/docs/1.10.1.RELEASE/reference/html/#repositories.query-methods.query-creation
So try with Page<NetworkSimple> findAll(Pageable pageable);
Even with spring-data-jpa 1.11.4, something like
public interface NetworkRepository extends JpaRepository<Network, String> {
Page<NetworkSimple> findAll(Pageable pageable);
}
would not compile; reporting
findAll(org.springframework.data.domain.Pageable) in NetworkRepository clashes with findAll(org.springframework.data.domain.Pageable) in org.springframework.data.repository.PagingAndSortingRepository
return type org.springframework.data.domain.Page<NetworkSimple> is not compatible with org.springframework.data.domain.Page<Network>
The workaround we found was to rename findAll to findAllBy, e.g.
public interface NetworkRepository extends JpaRepository<Network, String> {
Page<NetworkSimple> findAllBy(Pageable pageable);
}
You can use interface projection with Pageable like this :
Page<NetworkSimple> findPagedProjectedBy(Pageable pageable);
with some parameter :
Page<NetworkSimple> findPagedProjectedByName(String name, Pageable pageable);
Implementing interface projection with pagination
1. Our ResourceEntity.java class
#Getter
#Setter
#NoArgsConstructor
#Entity
public class ResourceEntity{
private Long id;
private String name;
}
2. Creating projection Interface name ProjectedResource.java, which maps data collected by the SQL query from repository layer method
public interface ProjectedResource {
Long getId();
String getName();
String getAnotherProperty();
}
3. Creating Repository layer method: getProjectedResources()
We are considering the database table name is resource.
We are only fetching id and name here.
#Query(name="select id, name, anotherProperty from resource", countQuery="select count(*) from resource", nativeQuery=true)
Page<ProjectedResource> getProjectedResources(Pageable page);
Hope the issue will be resolved!
You can use:
#Query("SELECT n FROM Network n")
Page<? extends NetworkSimple> findAllProjectedBy(Pageable pageable);

Resources