I have a simple query controller that takes query parameters (as Person dto) and a Pageable:
#RestController
public class PersonController {
#GetMapping("/persons")
public Object search(org.springframework.data.domain.Pageable pageable, Person form) {
repository.findAll(form, pageable);
}
}
The pageable can take both sort and page parameters as follows:
http://localhost:8080/persons?sort=age,desc&page=5
Problem: for the sort, I want to add order hints like NULLS_LAST or NULLS_FIRST.
Question: how can I achieve this using query params too?
You can modify Pageable object like this (method to put in your controller for example). This is an example with one sort field, but you can loop on all fields and modify nullsLast/nullsFirst in the same way. Please try :
private Pageable customSort(Pageable pageable) {
Sort sort = pageable.getSort();
Order order = sort.iterator().next();
List<Order> orders = new ArrayList<>();
// nullsFirst or nullsLast
orders.add(new Order(order.getDirection(),order.getProperty()).nullsFirst());
return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(orders));
}
Try to do something like :
pageable.getSort().and(Sort.by(new Sort.Order(Sort.Direction.DESC, "age", Sort.NullHandling.NULLS_LAST)));
Related
I have a global search I will search with one keyword but need to get results with all the matching columns of a table.
Page<A> a = null;
a = zRepo.getResultByNameSearch(searchText)
a = zRepo.getResultByNumberSeach(searchText)
a = zRepo.getRsultByProjectSearch(searchText)
#Query("select * from a_table x where x.name like :searchText")
Page<A> getResultByNameSearch(#Param("searchText") String searchText, Pageable pageable);
#Query("select * from a_table where x.number like :searchText")
Page<A> getResultByNumberSearch(#Param("searchText") String searchText, Pageable pageable);
#Query("select * from a_table where x.project like :searchText")
Page<A> getResultByProjectSearch(#Param("searchText") String searchText, Pageable pageable);
So each repository call queries and fetches the same table but according to the searchText.
Let's assume name = "Company910", number = "XX910", project = "910".
Now I'm searching for "910" and want to get results with all the 3 values. Page<a> will be having all the columns of a_table with the list of results as per "Company910", "XX910", "910".
How to implement this or is there any other way where I can maintain a single query instead of three different for name, number and project?
For the first part of how to get the combined result. I will do something like this.
Create repository interface as you wrote but using spring data jpa intead of raw query
user CompletableFuture with each method call
combine the result into a dto
return this combined result
#Repository
public interface SampleDocumentRepository extends JpaRepository<SampleDocument, Integer> {
Page<SampleDocument> findByNameContains(String name, Pageable pageable);
Page<SampleDocument> findByNumberContains(String number, Pageable pageable);
Page<SampleDocument> findByProjectContains(String project, Pageable pageable);
}
Service class where result are combined together
#Service
#AllArgsConstructor
public class SampleDocumentService {
private final SampleDocumentRepository repository;
#Transactional(readOnly = true)
public CompletableFuture<ResultDto> search(String query) {
PageRequest page = PageRequest.of(0, 20);
CompletableFuture<Page<SampleDocument>> nameSearch = CompletableFuture.supplyAsync(() -> repository.findByNameContains(query, page));
CompletableFuture<Page<SampleDocument>> numberSearch = CompletableFuture.supplyAsync(() -> repository.findByNumberContains(query, page));
CompletableFuture<Page<SampleDocument>> projectSearch = CompletableFuture.supplyAsync(() -> repository.findByProjectContains(query, page));
return CompletableFuture.allOf(nameSearch, numberSearch, projectSearch)
.thenApply(unused -> new ResultDto(nameSearch.join(), numberSearch.join(), projectSearch.join()));
}
}
Then the call from the service
#GetMapping("/search")
public CompletableFuture<ResultDto> search(#RequestParam("query") String query) {
return service.search(query);
}
call using your query argument
http://localhost:8080/sample/search?query=hello
To answer the second part, if you want to check if the query is present in any of the columns you can write JPA query combining the Or operator like this.
Page<SampleDocument> findByNameContainsOrNumberContainsOrProjectContains(String name, String number, String project, Pageable pageable);
Caller would be something like this
#Transactional(readOnly = true)
public CompletableFuture<ResultDto> searchAll(String query) {
PageRequest page = PageRequest.of(0, 20);
CompletableFuture<Page<SampleDocument>> nameSearch = CompletableFuture.supplyAsync(() ->
repository.findByNameContainsOrNumberContainsOrProjectContains(query, query, query, page));
return CompletableFuture.supplyAsync(() -> new ResultDto(nameSearch.join(), null, null));
}
If you want to use Async with spring data and completable please follow this link
So, idea is simple, I need to have an back-end sorting and filtering URL, smth like that:
/users?find=Paul&sortBy=name:asc
So far I have this controller method:
#GetMapping("/users")
public List<User> findUserByName(#RequestParam Optional<String> find,
#RequestParam Optional<Integer> page,
#RequestParam Optional<String> sort)
{
return userRepository.findByName(find.orElse("_"),
new PageRequest(page.orElse(0),5,
Sort.Direction.ASC, sort.orElse("name")));
}
And this UserRepository that extends JpaRepository:
#Query("select u from User u where name like %?1%")
List<User> findByName(String name, Pageable pageable);
My question is: I am specifying the sort status (Ascending/Descending) in the Controller itself, but I want it to be specified in url, like here:
/users?find=Paul&sortBy=name:asc
but for now it works for me only like that
/users?find=Paul&sortBy=name
and it gets sorted in ascending order automatically, because I specified it in the Controller method.
Could You be so kind to explain me how can I do that, please :)?
Take care!
you can use Direction.fromString(order) method where order can be "asc"
or "desc"
for order you should keep seperate param
#RequestParam Optional<String> order
userRepository.findByName(find.orElse("_"),
new PageRequest(page.orElse(0),5,
Sort.Direction.fromString(order), sort.orElse("name")));
You can add another optional request parameter to your controller (also it should be optional by setting required=false). In this way:
public enum SortType {
Dsc, Asc;
}
#GetMapping("/test/url")
public void handle(#RequestParam(required = false, defaultValue = "Asc") Optional<SortType> sort) {
switch (sort.get()) {
case Dsc:{/*sort descending*/}
default:{/*sort ascending*/}
}
}
In addition, 'sort' parameter can be used for calling repository methods canoditionally.
I am using projection, and I need to format the date variable in list of objects, but Page<> type response is read only, so I can not iterate and modify the objects. What Should I do?
#Override
public Page<OrderDto> findAll(Pageable pageable) {
return OrderDao.findByIsEnabledTrue(pageable, OrderDto.class);
}
I tried like this:
#Override
public Page<OrderDto> findAll(Pageable pageable) {
Page<OrderDto> page = orderDao.findByIsEnabledTrue(pageable, OrderDto.class);
List<OrderDto> orderDtos = page.getContent();
orderDtos.stream().forEach(it ->{
it.setFormattedCreatedAt(new SimpleDateFormat("dd-M-yyyy").format(it.getCreatedAt()));
});
page.getContent().clear();
page.getContent().addAll(orderDtos);
return page;
}
page.getContent() returns an unmodifiable list. The best approach is to use map method provided by page:
#Override
public Page<OrderDto> findAll(Pageable pageable) {
Page<OrderDto> page = orderDao.findByIsEnabledTrue(pageable, OrderDto.class);
page = page.map(this :: transformOrderDto);
return page;
}
private OrderDto transformOrderDto(final OrderDto order) {
order. setFormattedCreatedAt(new SimpleDateFormat("dd-M-yyyy").format(order.getCreatedAt()));
return order;
}
Since 1.10, Page has supported a map method that is specifically meant to let you transform the objects contained in it.
I have a rest API server which has the following API.
I have some other APIs, where i get pageable from GET requests. Here, I need to make a post request for passing the queryDto. So, I cannot pass the page=0?size=20 etc as url parameters.
I would like to know how to pass pageable as JSON object to a POST request
#RequestMapping(value = "/internal/search", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public ResponseList<Object> findObjects(#RequestBody QueryDto queryDto, Pageable pageable) {
if (queryDto.isEmpty()) {
throw new BadRequestException();
}
return someService.findObjectsByQuery(queryDto, pageable);
}
I Think that is not possible, at least not already provided by the framework.
The Spring has a HandlerMethodArgumentResolver interface with an implementation called PageableHandlerMethodArgumentResolver that retrieves the request param value calling something like HttpServletRequest.getParameter. So, you can bind the Pageable instance passing the parameters "page" and "size" for GET and POST. So, the following code works:
#RequestMapping(value="/test",method = RequestMethod.POST)
#ResponseBody
public String bindPage(Pageable page){
return page.toString();
}
$ curl -X POST --data "page=10&size=50" http://localhost:8080/test
Return:
Page request [number: 10, size 50, sort: null]
But, if you pass an json nothing happens:
$ curl -X POST --data "{page:10&size:50}" http://localhost:8080/test
Return:
Page request [number: 0, size 20, sort: null]
Spring Post method
#RequestMapping(value = "/quickSearchAction", method = RequestMethod.POST)
public #ResponseBody SearchList quickSearchAction(#RequestParam(value="userId") Long userId,
Pageable pageable) throws Exception {
return searchService.quickSearchAction(userId, pageable);
}
Postman Example:
http://localhost:8080/api/actionSearch/quickSearchAction?
userId=4451&number=0&size=20&sort=titleListId,DESC
In above POST Pageable is used for Sorting and Pagination in Spring RESTful service. Use below syntax at URL.
number 0, size 20, Sort by field titleListId and direction DESC
All passing parameter internally recognizes by Pageable as Sorting / Pagination parameters as below
number - Page number
size - Page Size
sort - sort by(Order by)
direction - ASC / DESC
Updated:
Angular Example:
CustomerComponent.ts file
let resultDesignations = null;
let fieldName = "designationId";
this.customerService.getDesignations(fieldName, "DESC").subscribe(
(data) => {
resultDesignations = data;
},
(err) => {
this.error(err.error);
},
() => {
this.designations = resultDesignations;
}
);//END getDesignations`
CustomerService.ts
getDesignations(fieldName: string, sortOrder: string): Observable<any> {
return this.httpClient.get("http://localhost:9876/api/getDesignations", {
params: {
sort: fieldName,sortOrder
}
});
}
It seems to work just fine for me if you continue to provide them as query parameters on the URL, and still post data in.
POST http://localhost:8080/xyz?page=2&size=50
Content-Type: application/json
{
"filterABC": "data"
}
Spring seems to translate the page, size, sort etc. into the Pageable provided to the method on the way in.
create a class that has the Pageable and QueryDto objects as members. Then pass JSON in the post body of this new object.
for example,
public class PageableQueryDto
{
private Pageable pageable;
private QueryDto queryDto;
... getters and setters.
}
Edit
As noted in the comment below, you may need to implement the Pageable interface.
The result could be something like this:
public class PageableQueryDto implements Pageable
{
private Pageable pageable;
private QueryDto queryDto;
... getters and setters.
... Implement the Pageable interface. proxy all calls to the
... contained Pageable object.
... for example
public void blam()
{
pageable.blam();
}
... or maybe
public void blam()
{
if (pageable != null)
{
pageable.blam();
}
else
{
... do something.
}
}
sample example
#RequestMapping(path = "/employees",method = RequestMethod.POST,consumes = "application/json",produces = "application/json")
ResponseEntity<Object> getEmployeesByPage(#RequestBody PageDTO page){
//creating a pagable object with pagenumber and size of the page
Pageable pageable= PageRequest.of(page.getPage(),page.getSize());
return ResponseEntity.status(HttpStatus.ACCEPTED).body(employeeRegistryService.getEmployeesByPage(pageable));
}
In your case try to add pagination variables in QueryDTO
create a Pageable object and pass it to service
I think that will solve :)
I have some infromation coming from request such as:
http://localhost:9080/online/accounts/list/usersQuery?filter=uid&value=ab
And I have to treat this in Spring where the object is uid and the filter value is ab
So far I have the folowing code in Spring:
#RequestMapping(produces="application/json", value = "/usersQuery", method=RequestMethod.GET)
public #ResponseBody PagedResources<Resource<UserDetails>> listItemsSortQuery(#PageableDefault(size = 20, page = 0) Pageable pageable, PagedResourcesAssembler<UserDetails> assembler) {
Page<UserDetails> lstUserDetails = userDetailsRepository.findAll(pageable);
return assembler.toResource(lstUserDetails);
}
But it doesn't consider nothing about those two values.
What should I change in order to filter data according to the field uid and filter data ab ?
The uid is the user id in the user object and I need to pick all the users that have an id containing ab
Any help would be apreciated.
Try getting uid value with #RequestParam
#RequestMapping(produces="application/json", value = "/usersQuery", method=RequestMethod.GET)
public #ResponseBody PagedResources<Resource<UserDetails>> listItemsSortQuery(#PageableDefault(size = 20, page = 0) Pageable pageable, PagedResourcesAssembler<UserDetails> assembler,
#RequestParam("filter")String filter, #RequestParam("value")String value) {
Page<UserDetails> lstUserDetails = userDetailsRepository.findByFilter(pageable, filter, value);
return assembler.toResource(lstUserDetails);
}
EDITED:
In your repository you need a method to filter your data, i.e.
public interface UserDetailsRepository extends JpaRepository<UserDetails, Long> {
#Query("SELECT u FROM UserDetails u WHERE LOWER(?1) = LOWER(?2)")
Page<UserDetails> findByFilter(String filter, String value, Pageable pageable);
}