Annotation Query and return only specific nested field with spring data elasticsearch - spring-boot

Annotation Query and return only specific nested field with spring data elasticsearch
Version:
springboot:2.1.7.RELEASE
spring-data-elasticsearch: 2.1.7.RELEASE
elasticsearch: 6.5.4
document:
#Data
#Document(indexName = "test_book", type = "test_book")
public class Book {
#Id
private String id;
private String name;
private LocalDateTime time;
/**
*
*/
private Publishing publishing;
/**
*
*/
private Author author;
}
Repository:
public interface BookRepository extends ElasticsearchRepository<Book,String> {
#Query("{" +
"\"_source\": {" +
" \"includes\": " +
" [ \"name\"]" +
"}," +
"\"bool\" : {" +
" \"must\" : [{" +
" \"term\" : {" +
" \"id.keyword\" : \"?0\"" +
" }" +
" }]" +
" }" +
"}")
Book queryBookNameById(String id);
}
I just want to get the data of the name, which can relatively save memory resources. But I got an error, can’t I use it like this? Or can only use elasticsearchTemplate?
ParsingException[no [query] registered for [_source]
]
at org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder(AbstractQueryBuilder.java:332)
at org.elasticsearch.index.query.WrapperQueryBuilder.doRewrite(WrapperQueryBuilder.java:165)
at org.elasticsearch.index.query.AbstractQueryBuilder.rewrite(AbstractQueryBuilder.java:279)
at org.elasticsearch.search.builder.SearchSourceBuilder.rewrite(SearchSourceBuilder.java:921)
at org.elasticsearch.search.builder.SearchSourceBuilder.rewrite(SearchSourceBuilder.java:80)
at org.elasticsearch.index.query.Rewriteable.rewriteAndFetch(Rewriteable.java:97)
at org.elasticsearch.index.query.Rewriteable.rewriteAndFetch(Rewriteable.java:87)
at org.elasticsearch.action.search.TransportSearchAction.doExecute(TransportSearchAction.java:215)
at org.elasticsearch.action.search.TransportSearchAction.doExecute(TransportSearchAction.java:68)
at org.elasticsearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:167)
at org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.apply(SecurityActionFilter.java:126)
......

This currently does not work with the #Query annotation for repositories. Spring Data Elasticsearch will wrap the value of the annotation as the query value and so includes the _source part into the query.
We would need to add additional parameters to the annotation (includes and excludes) to be able to build a correct query and make this work. I will create an Jira issue for this to keep track of this feature request.
Jira issue

Related

Adding and removing dynamically SQL WHERE clause using JPA [duplicate]

Is there a way in Spring data to dynamically form the where clause?
What I want to do is have a method (which is like the findBy / get method) which runs a WHERE and AND using the mentioned properties which are NOT NULL.
For example,
Consider the object Person [firstName, lastName, age, gender]
Our method looks something like this
findBy_IfNotNullFirstName_AndIfNotNullLastName_AndIfNotNullAge_AndIfNotNullGender(String firstName, String lastName, Integer age, String gender)
Thanks.
A simpler option is to test if the parameter is null right in the JPQL query:
Exemple from my project:
#Query("select m from MessageEntity m " +
"join fetch m.demandeAnalyseEntities d " +
"where (:patientId is null or d.noPtn= :patientId) " +
" and " +
" ( :labNbr is null or d.noLab= :labNbr) " +
" and " +
" ( :reqDate is null or d.dteReq= :reqDate) " +
" and " +
" ( :reqNum is null or d.noReq= :reqNum) "
)
List<MessageEntity> findMessagesWithDemandesOnly(#Param("patientId") Long pid,
#Param("labNbr") Integer labNo,
#Param("reqDate") String reqDate,
#Param("reqNum") Integer reqNum,
Pageable pageable);
Take a look at JPA Specification and Predicate, and Even better QueryDSL, there both supported by spring data repositories.
This article provide an example:
http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
Another solution: You can extend your JPA repo interface using custom fragment interfaces.
Define your custom methods on a new interface
public interface PersonFragRepository {
List<User> findPersonByWhatever(
String firstName, String lastName, String age, String gender);
}
Provide the implementation
public class PersonFragRepositoryImpl implements PersonFragRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
List<User> findPersonByWhatever(
String firstName, String lastName, String age, String gender) {
...
}
}
Extends your JPA interface
public interface PersonRepository
extends JpaRepository<Person, Integer>, PersonFragRepository

Map resultset to non-entity Spring data Jpa

I have a Spring Batch application and need to make a select from 4 tables. The problem, that i cant use relations, and will write enrichers to select full data.
Currently i have few problems:
Is it possible to map non entity (POJO) from JpaRepository in simple way. I seen examples with #SqlResultSetMapping. I am looking for auto mapping (Maybe JPA, not Spring Data). Column names i can make exacly with result, but it is not working.
It is no problem to mark POJO as Entity, but there is problem. Entity requires #Id. And this is problem for me, as my enrich data not suitable for this.
Maybe someone will give me tips, which better to use.
My perfect purpose is do something like this.
#Query(value = "SELECT id as main_id FROM table where name = ?1",
nativeQuery = true)
List<Data> getAll(String name);
And get objects mapped to my POJO.
You can use JPA constructor expression:
Example:
public class CallStatistics {
private final long count;
private final long total;
private final int min;
private final int max;
private final double avg;
public CallStatistics(long count, long total, int min, int max, double avg) {
this.count = count;
this.total = total;
this.min = min;
this.max = max;
this.avg = avg;
}
//Getters and setters omitted for brevity
}
CallStatistics callStatistics = entityManager.createQuery(
"select new org.hibernate.userguide.hql.CallStatistics(" +
" count(c), " +
" sum(c.duration), " +
" min(c.duration), " +
" max(c.duration), " +
" avg(c.duration)" +
") " +
"from Call c ", CallStatistics.class)
.getSingleResult();
The importants part are:
You must have a class with a matching constructor
You must use NEW followed by the fully qualified class name of the class
From:
https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#hql-select-clause

spring boot data #query to DTO

I want to assign the result of a query to a DTO object. The DTO looks like this:
#Getter
#Setter
#NoArgsConstructor
public class Metric {
private int share;
private int shareholder;
public Metric(int share, int shareholder) {
this.share = share;
this.shareholder = shareholder;
}
}
And the query looks like the following:
#RepositoryRestResource(collectionResourceRel = "shareholders", path = "shareholders")
public interface ShareholderRepository extends PagingAndSortingRepository<Shareholder, Integer> {
#Query(value = "SELECT new com.company.shareholders.sh.Metric(SUM(s.no_of_shares),COUNT(*)) FROM shareholders s WHERE s.attend=true")
Metric getMetrics();
}
However, this didn't work, as I got the following exception:
Caused by:org.hibernate.QueryException: could not resolve property: no_of_shares of:com.company.shareholders.sh.Shareholder[SELECT new com.company.shareholders.sh.Metric(SUM(s.no_of_shares),COUNT(*)) FROM com.company.shareholders.sh.Shareholder s WHERE s.attend=true]
In my project I've used projections to this like shown below:
#Repository
public interface PeopleRepository extends JpaRepository<People, Long> {
#Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
List<PeopleDTO> findByPeopleAndCountByUserId(#Param("userId") Long userId);
#Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
Page<PeopleDTO> findByPeopleAndCountByUserId(#Param("userId") Long userId, Pageable pageable);
}
The interface to which the result is projected:
public interface PeopleDTO {
String getName();
Long getCount();
}
The fields from the projected interface must match the fields in this entity. Otherwise field mapping might break.
Also if you use SELECT table.column notation always define aliases matching names from entity as shown in example.
In your case change #Query like shown below:
#Query(value = "SELECT new " +
"SUM(s.no_of_shares) AS sum,COUNT(*) AS count FROM " +
"shareholders s WHERE s.attend=true", nativeQuery = true)
MetricDTO getMetrics();
And create interface MetricDTO like shown below:
public interface MetricDTO {
Integer getSum();
Long getCount();
}
Also make sure the return type of getSum() and getCount() is correct this may vary based not database.
First, you can have a look at the Spring Data JPA documentation, you can find some help at this section : Class-based Projections (DTOs).
There is also a paragraph titled Avoid boilerplate code for projection DTOs, where they advise you to use Lombok's #Value annotation, to produce an immutable DTO. This is similar to Lombok's #Data annotation, but immutable.
If you apply it to your example, the source will look like :
#Value
public class MetricDto {
private int share;
private int shareholder;
}
Then, as your query is a NativeQuery, specifiy it in your Spring Data Repository.
You can find help in the documentation : Native Queries.
You will need something like :
#Query(value = "SELECT new
com.company.shareholders.sh.MetricDto(SUM(s.no_of_shares),COUNT(*)) FROM
shareholders s WHERE s.attend=true", nativeQuery = true)
MetricDto getMetrics();
Query query = sessionFactory.getCurrentSession()
.createNativeQuery(stringQuery).unwrap(org.hibernate.query.Query.class);
((NativeQueryImpl) query).setResultTransformer(new AliasToBeanResultTransformer(DtoClass.class));
You are writing a mixed query of native and jpql; no_of_shares is your column name in the database, but jpa is expecting you to provide not native syntax so try to replace no_of_shares with the corresponding field in your entity class. Or just add nativeQuery = true to make jpa understand it's a native query.

Error to convert objects using #SqlResultMapping and #ConstructorResult to NamedNativeQuery

guys.
I'm facing this problem for a day and I couldn't find a solution.
I have this scenario:
Spring Boot
JPA 2.1
Hibernate
I'm using Repository
The app is a JSON REST service
and this code, my entity Ticket.java:
#Entity
#Table(name = "tickets")
#SqlResultSetMapping(
name="SummaryReport",
classes=#ConstructorResult(
targetClass=ValidatedTicketsVO.class,
columns={#ColumnResult(name="quantity",
type=BigInteger.class),
#ColumnResult(name="quarter", type=String.class)
}))
#NamedNativeQuery(
name="Ticket.findShowSummaryReport",
query="SELECT COUNT(*) AS quantity, DATE_FORMAT( FROM_UNIXTIME(UNIX_TIMESTAMP(validation_time) - UNIX_TIMESTAMP(validation_time)%(15*60)), \"%d/%m/%Y %H:%i\" ) AS quarter " +
" FROM tickets " +
" WHERE show_id = :showId " +
" GROUP BY quarter " +
" ORDER BY quarter ")
public class Ticket implements Serializable {
I also tried using #EntityResult instead of #ConstructorResult:
#SqlResultSetMapping(
name="SummaryReport",
entities=#EntityResult(
entityClass=ValidatedTicketsVO.class,
fields={#FieldResult(name="quantity", column="quantity"),
#FieldResult(name="quarter", column="quarter")
}))
but I got the same error.
and my POJO / VO class that query result has to be mapped to:
import java.math.BigInteger;
public class ValidatedTicketsVO {
private BigInteger quantity;
private String quarter;
public ValidatedTicketsVO(BigInteger quantity, String timeQuarter) {
super();
this.quantity = quantity;
this.quarter = timeQuarter;
}
[ getters and setters ]
}
PS: the field quantity was initially defined as Integer but as I got a [java.math.BigInteger] exception I changed it to Long and now to BigInteger to see if it solves my problem, but stills the same.
And here is my Repository method:
List<ValidatedTicketsVO> findShowSummaryReport(#Param("showId") Long showId);
The query is well executed and I can't see any other log rather than the query itself. I get no exception in the log, but I get this response when I try to execute it through Postman:
{
"code": 5001,
"description": "Unexpected server error",
"timestamp": 1499782989890,
"errors": 1,
"fieldErrors": [
{
"field": "",
"message": "No converter found capable of converting from type [java.math.BigInteger] to type [br.com.company.validator.model.entity.vo.ValidatedTicketsVO]"
}
]
}
When I debug it on Eclipse the exception occurs when I call this method:
List<ValidatedTicketsVO> list = repository.findShowSummaryReport(showId);
Can someone help me, please?

query with map parameter in spring data jpa

I am using spring data jpa. I noticed spring data jpa supports spel as query parameter recently. It works when using object property as parameter, but Map didn't work.
it's code snippet below
#Query(value = "select item from BoardItem item " +
"where item.board.id = :#{#search.addition['boardId']} " +
"and ( " +
" item.title like concat('%', :#{#search.value}, '%') " +
" or item.contents like concat('%', :#{#search.value}, '%') " +
" or item.writer like concat('%', :#{#search.value}, '%') " +
")"
)
#Override
Page<BoardItem> findAll(#Param("search") SearchVo searchVo, Pageable pageable);
public class SearchVo {
private String value;
private Boolean regex;
private Map<String, String> addition;
....
new SearchVo().getAddition().put("boardId", "1000");
#{#search.value} works, but #{#search.addition['boardId']} isn't.
Can anyone let me know if it works with using spel like #{#search.addition['boardId']} ?
Thanks in advance.

Resources