Spring Data Pageable breaking Spring Data JPA OrderBy - spring

I have a simple JpaRepository with a finder that returns records ordered by a property named "number" in descending order. The "number" property is also the #Id of my entity. This works just fine, however there's thousands of records, so I want to return a Page instead of a List.
#Repository
public interface ReceiptRepository extends JpaRepository<Receipt, BigDecimal> {
#Query
public List<Receipt> findByStorerOrderByNumberDesc(String storer);
}
If I change my finder to something like the following, the sorting no longer works. I've tried using the sort capability of the Pageable argument, but it didn't work. Also removed the OrderByNumberDesc, but same result.
#Repository
public interface ReceiptRepository extends JpaRepository<Receipt, BigDecimal> {
#Query
public Page<Receipt> findByStorerOrderByNumberDesc(String storer, Pageable pageable);
}
EDIT - added controller method
The following is my controller method.
#RequestMapping(method = RequestMethod.GET, produces = {"application/json"})
public PagedResources<Receipt> receipts(Pageable pageable, PagedResourcesAssembler assembler) {
Page<Receipt> receipts = receiptRepository.findByStorer("003845", pageable);
return assembler.toResource(receipts, receiptResourceAssembler);
}
I feel I'm missing something very basic here.
I'm using Spring Data JPA 1.5.2 and Commons 1.7.2.
Thanks :)

Add the sort to your Pageable when you create it:
e.g.
Pageable pageable ...;
pageable.getSort().and(new Sort(Direction.ASC, "prop1", "prop1"));
or in Spring MVC you can do:
#RequestMapping(method = RequestMethod.GET, produces = {"application/json"})
public PagedResources<Receipt> receipts(#PageableDefaults(sort = { "x",
"y", "z" }, value = 10)Pageable pageable, PagedResourcesAssembler assembler) {
Page<Receipt> receipts = receiptRepository.findByStorer("003845", pageable);
return assembler.toResource(receipts, receiptResourceAssembler);
}

Related

Spring JPA: find by multiple IDs with Pagination

How is it possible to apply the pagination to the below query:
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query("select b from Building b where b.id in :ids" )
Page<Post> findByIds(#Param("ids") List<Long> postIdsList);
...
}
All the existing examples are based on the standard findAll method that accepts a Pageable object: public Page findAll(Pageable pageable);.
The questions are:
what the controller method signature should be
what the repository method parameters should be
how and what parameters should be passed into the controller method
should I always split the post IDs for every request
will Spring make a single query and keep all the found posts in memory or it will hit a query every time for every next/previous page? If so, how can it figure out the IDs to use to find the next/previous posts?
The initial implementation was as follows:
#RestController
class PostsController {
#Autowired
private PostService postService;
#GetMapping("/posts", params = "ids")
public List<Post> getPaginatedPosts(#RequestParam List<Long> ids) {
return postService.findPaginatedPosts(ids);
}
}
#Repository
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query("select b from Building b where b.id in :ids" )
Page<Post> findByIds(#Param("ids") List<Long> postIdsList);
...
}
I omitted the code from the PostServiceImpl qui implements the PostService and just calls the PostRepository#findByIds method.
Try this:
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query( "select o from Building b where id in :ids" )
Page<Post> findByIds(#Param("ids") List<Long> postIdsList,Pageable pageRequest);
...
}
In controller ask for pageSize and pageNo, if it is empty set a default value like pageNo = 0, pageSize=10.
pass these values to to service layer service should create pageable object call findByIds(ids, pagable); and return the page to controller.
you can refer this:
https://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-part-seven-pagination/
Here is the solution I came to you coupled with the above comments suggestions.
Define a repository either extending JpaRepository or PagingAndSortingRepositoryas follows:
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query("select p from Post p where p.id in :ids" )
Page<Post> findByIds(#Param("ids") List<Long> postIdsList);
...
}
Create a service class and its implementation:
public interface PostService {
List<PostDTO> getPostsList(List<Long> ids, Pageable pageable);
...
}
#Service
#Slf4j
public class PostServiceImpl implements PostService {
...
#Autowired
private PostRepository postRepository;
...
#Override
public List<PostDTO> getPostsList(List<Long> ids, Pageable pageable) {
List<PostDTO> resultList = new ArrayList<>();
Page<Post> paginatedPosts = postRepository.findByIds(ids, pageable);
List<Post> posts = paginatedPosts.getContent();
posts.forEach(post -> resultList.add(convertToPostDTO(post)));
return resultList;
}
And finally, the PostsController part:
#RestController
#RequestMapping("/api")
class PostsController {
#Autowired
private PostService postService;
...
#GetMapping(value = "/posts", params = "ids")
public ResponseEntity <List<PostDTO>>getPostsList(#RequestParam List<Long> ids, Pageable pageable) {
List<PostDTO> postsList = postService.getPostsList(ids, pageable);
return new ResponseEntity<>(postsList, HttpStatus.OK);
}
The request should contain page and size URL parameters (by default, page is 0 and size is 20):
http://localhost:8080/api/posts?ids=1050,1049,1048,1043,1042,1041,1040,1039,1038&size=5&page=1&sort=id
In the above example, I had 9 records total and I put the parameters explicitly to limit the result list to 5 and display the second page only as well as to sort them by id.
If you don't provide them, the default values will be used (page = 0, size = 20).
To anyone coming here looking to pass a list of ids as a url-parameter like the question asker wants to do and the answer of belgoros explains:
Be aware of the url-max-length of 2048 characters.
So if your list of ids is long enough to require pagination, you probably also want to make the ids a body-parameter. This answer explains how to create body-parameters with spring: https://stackoverflow.com/a/22163492/7465516
I think this is important, because solutions that work on small data but unexpectedly fail on big data are the kind of thing that gets through testing and fails in production.
(I do not have the reputation to make this a comment, I hope this post is acceptable)
#Query( "select o from Building b where id in :ids", nativeQuery=true )
Page findByIds(#Param("ids") List postIdsList,Pageable pageRequest);

Oracle Parallel Hint on Spring Data JPA Specification

Is it possible to use parallel hint on the spring data jpa specification? org.hibernate.jpa.QueryHints package does not contain PARALLEL hint.
I added a comment like in the example below. But I couldn't see the running query.
Calling find method with specifications:
CarSpecifications carSpecifications = new CarSpecifications(car);
List<Car> carList = carRepository.findAll(carSpecifications, pageable);
Repository:
#Override
public Page<T> findAll(Specification<T> spec, Pageable pageable) {
TypedQuery<T> query = getQuery(spec, pageable);
query.setHint("org.hibernate.comment", "Example Comment");
return pageable == null ? new PageImpl<T>(query.getResultList()) : readPage(query, pageable, spec);
}
or in the interface
#QueryHints({ #QueryHint(name = "org.hibernate.comment", value ="Example Comment") })
Page<T> findAll(Specification<T> spec, Pageable pageable);
Thanks.

How can I use Spring's pagination (using Pageable) while writing a dynamic query using QueryDSL?

I am trying to use pagination with QueryDSL - using the com.mysema.querydsl package.
All my Querydsl query types look like this -
#Generated("com.mysema.query.codegen.EntitySerializer")
public class QCountry extends EntityPathBase<Country> {...}
Currently, my repository implementation class looks something like this -
#Override
public Page<Country> findPaginatedCountries(String country, Optional<String> status, Pageable pageable) {
QCountry qCountry= QCountry.someObject;
QActiveCountry qActiveCountry = QActiveCountry.activeCountry;
JPAQuery jpaQuery = new JPAQuery(entityManager);
QueryBase queryBase = jpaQuery.from(qCountry).innerJoin(qActiveCountry).fetch()
.where(qCountry.codeLeft.country.upper().eq(country.toUpperCase()))
.where(qCountry.codeRight.country.upper().eq(country.toUpperCase()));
if(status.isPresent()){
queryBase = queryBase.where(qActiveCountry.id(qCountry.active.id))
.where(qActiveCountry.status.upper().eq(status.get().toUpperCase()));
}
.......}
Now, I want this dynamic query to return a paginated response. I want to use Spring's pagination to do that and not manually set offset, size etc.
I know I can use QueryDslRepositorySupport class - as implemented here - https://github.com/keke77/spring-data-jpa-sample/blob/master/spring-data-jpa/src/main/java/com/gmind7/bakery/employee/EmployeeRepositoryImpl.java
Sample code from the above link -
#Override
public Page<Employees> QFindByOfficeCode(long officeCode, Pageable pageable) {
//JPAQuery query = new JPAQuery(em);
JPQLQuery query = from(QEmployees.employees).where(QEmployees.employees.officeCode.eq(officeCode));
query = super.getQuerydsl().applyPagination(pageable, query);
SearchResults<Employees> entitys = query.listResults(QEmployees.employees);
return new PageImpl<Employees>(entitys.getResults(), pageable, entitys.getTotal());
}
However, to do that -
I need to pass JPQLQuery object to the applyPagination method. How can I do that without changing my code (Ofcourse, the repository class will extend QueryDslRepositorySupport class). Currently, I am using JPAQuery as you can see.
OR
I probably need to change my QueryDSL types by having them extend EntityPath instead of EntityPathBase so that I can use JPQLQuery.from() to generate the query and then use the applyPagination method, which requires a JPQLQuery object. However, my Q classes are extending EntityPathBase class instead. Should I be use com.querydsl package instead of com.mysemsa.querydsl package to generate query types?
OR
Other option is to use the following - http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/querydsl/QueryDslPredicateExecutor.html#findAll-com.querydsl.core.types.Predicate-org.springframework.data.domain.Pageable-
Code snippet below -
Page<T> page = QueryDslPredicateExecutor.findAll(org.springframework.data.querydsl.Predicate predicate, Pageable pageable)
However, I am making joins between two tables and then filtering results with a where clause (as you can see above in my code). How can I pass a predicate object in the findAll method above? Not sure how to include a join in it.
Please let me know if the problem is not clear, I can add more details.
EDIT: There is a many to one relationship between Country and ActiveCountry.
Country class has an ActiveCountry reference. And we have to do a join between both ids. Is is possible that Country can have null ActiveCountry. Therefore, we want an inner join - only non null values for active country
#ManyToOne
#JoinColumn(name="id")
ActiveCountry active;
Step 1: Annotate the entity class with #QueryEntity
#Entity
#QueryEntity
public class Country {}
This seems to have been addressed already since the question shows Q classes.
Step 2: Have the repository interface extend QueryDslPredicateExecutor
public interface CountryRepository
extends PagingAndSortingRepository<Country, Long>
, QueryDslPredicateExecutor<Country> {
}
Step 3: Invoke the Page<T> findAll(Predicate query, Pageable page) method provided by QueryDslPredicateExecutor
public Page<Country> getCountries(String country, Optional<String> status, Pageable page) {
QCountry root = QCountry.country;
BooleanExpression query = root.codeLeft.country.equalsIgnoreCase(country);
query = query.and(root.codeRight.country.equalsIgnoreCase(country));
if (status.isPresent()) {
query = query.and(root.active.status.equalsIgnoreCase(status));
}
return countryRepository.findAll(query, page);
}

How to write a findByCritera1InAndCritera2In using (spring data) CrudRepository with parameters being optional

I'm using spring boot with spring data, specifically the class PagingAndSortingRepository that extends CrudRepository.
I need a query which returns all entries of a table if matches either of the four lists ignoring it when it is null (or empty).
If I use findByTypeInAndLocaleInAndCategoryInAndTagIn and one of the lists is empty, the result is empty as well. So I ended up writing several finders and depending on which lists are empty using a different one. Is it possible to combine this in one finder?
So e.g. if I use findByTypeAndLocale I'd like to match all values of type if the list type is empty.
Happy about any hints.
#Repository
public interface FeedRepository extends PagingAndSortingRepository<FeedEntry, Long>, JpaSpecificationExecutor<FeedEntry> {
public List<FeedEntry> findByGuid(String guid);
public Page<FeedEntry> findAll(Pageable pageable);
public Page<FeedEntry> findByLocale(List<LocaleEnum> type, Pageable pageable);
public Page<FeedEntry> findByType(List<FeedTypeEnum> type, Pageable pageable);
public Page<FeedEntry> findByTypeAndLocale(FeedTypeEnum type, LocaleEnum locale, Pageable pageable);
public Page<FeedEntry> findByTypeInAndLocaleIn(List<FeedTypeEnum> type,List<LocaleEnum> locale, Pageable pageable);
public Page<FeedEntry> findByTypeInAndLocaleInAndCategoryIn(List<FeedTypeEnum> type,List<LocaleEnum> locale, List<String> category, Pageable pageable);
public Page<FeedEntry> findByTypeInAndLocaleInAndTagIn(List<FeedTypeEnum> type,List<LocaleEnum> locale, List<String> tag, Pageable pageable);
public Page<FeedEntry> findByTypeInAndLocaleInAndCategoryInAndTagIn(List<FeedTypeEnum> type,List<LocaleEnum> locale, List<String> category, List<String> tag, Pageable pageable);
}
You cannot do it as you are doing. If the list is empty means there isn't any value which matches with your condition query.
To do what you are looking for you need to do it with QBE (Query By example) which is compatible with CrudRepository
QBE doc
Why? You need a dynamic query and as the doc says:
Query by Example (QBE) is a user-friendly querying technique with a
simple interface. It allows dynamic query creation and does not
require to write queries containing field names. In fact, Query by
Example does not require to write queries using store-specific query
languages at all.
An example of the doc:
public interface PersonRepository extends JpaRepository<Person, String> { … }
public class PersonService {
#Autowired PersonRepository personRepository;
public List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
**Making example for your case...
I'm affraid it is not possible at the moment.
IMHO the best way you can achieve your goal is to use Spring Data JPA Specifications (http://docs.spring.io/spring-data/jpa/docs/1.10.2.RELEASE/reference/html/#specifications) and manually check every parameter for not null value...

Spring rest controller and paging

I use spring 4.2 and rest and I would like to use paging.
What is the way to use paging with spring rest controller?
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public Page<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, Pageable pageable) {
return paymentService.getPaymentByMemberId(memberId, pageable);
}
Is it a good way to manage this?
If for some area in the application, we don't want to use paging, We need to create another url?
if I want all payments for a member, I will do:
/members/{memberId}/payments
and for the paging, it's there a way to said to spring to do something like:
/members/{memberId}/payments?pageNumber=1&PageSize=10
One way to do this is:
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public List<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, #RequestParam(value = "pageNumber", required = false) final Integer pageNumber,#RequestParam(value = "pageSize", required = false) final Integer pageSize) {
PageRequest pageReq = new PageRequest((pageNumber == null ? 0 : pageNumber), (pageSize == null ? 0 : pageSize));
Page<PaymentDto> page = paymentService.getPaymentByMemberId(memberId, pageReq);
return page.getContent();
}
You need write annotation #RestController for your controller
#RestController
public class PaymentController {
...
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public Page<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, Pageable pageable) {
return paymentService.getPaymentByMemberId(memberId, pageable);
}
}
Request example: /members/12345/payments?page=0&size=50

Resources