Spring boot search filter and pagination - spring

I want to use pagination, filter and search in a single endpoint of a RestController.
I am using spring 5. Can anyone suggest what will the best approach for this solution.

public class SearchFilter {
private String searchValue;
#Min(0)
private int offset;
#Min(1)
private int limit;
}
#RestController
#RequestMapping("/texts")
public class MyController {
#PostMapping("/search")
public ResponseEntity<List<Text>> search(#RequestBody #Valid SearchFilter filter) {
Query query = new Query(Criteria.where("text").regex(Pattern.compile(filter.getSearchValue()).pattern(), "i"))
.skip(filter.getOffset())
.limit(filter.getLimit());
...
Regarding the pagination (kind of limit and offset above), you can use also #RequestParam(required=false) for the pageSize and pageNumber, and then do if possible PageRequest.of(pageNumber, pageSize).

Related

How to validate the range of a query parameter on Spring Boot

There's a payload that I need to paginate and now it works but I want to validate the range of limit query parameter. I tried adding the #Range annotation but it doesn't seem to work. Can someone help me to validate this?
#GetMapping("/ambassadors")
#ResponseStatus(value = HttpStatus.OK)
public Page<Ambassador> getAmbassadors(#Range(min = 10, max = 20) #RequestParam int limit,
#Range(min = 0) #RequestParam int pageNumber) {
Pageable pageable = PageRequest.of(pageNumber, limit);
return ambassadorRepository.findAll(pageable);
}
Try adding #Validated annotation to your controller.
ex:
#RestController
#Validated
public class TestController {
}

Spring boot + DocumentDB custom Query for searching documents

I have Spring Boot + DocumentDB application in which we need to implement a search API, The Search fields are part of nested Json are as below:
{
"id":"asdf123a",
"name":"XYZ",
add{
"city":"ABC"
"pin":123456,
}
}
I need to search with name="XYZ" and city="ABC", I'm trying with below code but somehow not able to retrieve the record.
But I'm able to retrieve with just by Name or ID, but not with name and city or just city which is part of nested JSON.
Employee{
private String id;
private String name;
private Address add
/*Getter and Setters {} */
}
Address{
private String city;
private Long pin;
/*Getter and Setters {} */
}
public class EmployeeController {
EmployeeRepository repository;
#Autowire
EmployeeController (EmployeeRepository repository){
this.repository = repository;
}
#GetMapping(value = "/search", produces = APPLICATION_JSON_VALUE_WITH_UTF8)
public ResponseEntity<?> Search (#RequestParam ("name")String name,
#RequestParam("city") String city){
return new ResponseEntity <> (repository
.findByNameLikeAndAddressCityLike(
name, city),
HttpStatus.OK
);
}
}
#Repository
public interface EmployeeRepository extends DocumentDbRepository<Employee,
String> {
Optional<Employee>findByNameLike(String name); // Perfectly working
Optional<Employee>findByAddressCityLike(String city); // Not working
Optional<Employee>findByNameLikeAndAddressCityLike(String name, String
city); // Not Working
}
Also Just like Spring JPA we use #Query to fire custom/ native query are there any DocumentDB Annotation present if so please guide me with example or Docuemnt. Looking for help

Filtering with Spring Data Projection

I've created a class based Projection for my Spring Data Repository. That works great. Then I tried to annotate the constructor with #QueryProjection from QueryDSL, hoping to get a REST Endpoint with paging, sorting and filtering.
The code looks like this, but there are way more fields and details omitted for brevity:
Entity:
#Data
#Entity
public class Entity extends BaseEntity {
private String fieldA, fieldB;
private AnotherEntity ae;
}
DTO:
#Getter
#FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class EntityDto {
private final String fieldA;
private final String anotherEntityFieldC;
#QueryProjection
public EntityDto(final String fieldA, final String anotherEntityFieldC) {
this.fieldA = fieldA;
this.anotherEntityFieldC = anotherEntityFieldC;
}
}
Repository:
public EntityRepo extends JpaRepository<Entity, Long>, QuerydslBinderCustomizer<EntityPath<Entity>>, QuerydslPredicateExecutor<Entity> {
Page<EntityDto> findPageProjectedBy(Predicate predicate, Pageable pageable);
}
Endpoint:
#RestController
#RequestMapping(EntityEndpoint.ROOT)
#RequiredArgsConstructor(onConstructor = #__({#Autowired}))
public EntityEndpoint {
private final EntityRepo er;
#GetMapping
public ResponseEntity<PagedResources<Resource<EntityDto>>> getAllEntities(
Pageable pageable,
#QuerydslPredicate(root = EntityDto#.class) Predicate predicate,
PagedResourcesAssembler<EntityDto> assembler) {
Page<EntityDto> page = er.findPageProjectedBy(predicate, pageable);
return new ResponseEntity<>(assembler.toResource(page), HttpStatus.OK);
}
}
I get the exception:
java.lang.IllegalStateException: Did not find a static field of the same type in class at.dataphone.logis4.model.dto.QBuchungDto!
Stacktrace as gist
And that's the URL:
curl -X GET --header 'Accept: application/json' 'http://localhost:8080/Entity'
Well, it seems like you can only query with the actual Entity.
I think of it as the part in the 'WHERE'-clause, which can only have columns actually in the selected Entities, while the DTO projection happens in the 'SELECT'-clause.
The #QueryProjection-Annotation seems to serve only for the QueryDsl code generator to mark class which then can be used to create projections in a select-clause

Filter results from QueryDSL search

I'm using QueryDSL as part of Spring Data Rest to search entities from our API.
Is it possible to somehow filter the search API, so that by default it won't find for example Car entities that are "deactivated"?
Currently I've a flag on car entity that when it's set to true, it shouldn't be exposed through our search API, and the cars that have this property set should be left out from search.
https://docs.spring.io/spring-data/jpa/docs/1.9.0.RELEASE/reference/html/#core.web.type-safe
In case of using Spring Data REST and QueryDSL, to change standard behavior of queries we can use aspects.
For example: we need to show by default only those Models whose flag is set to true:
#Data
#NoArgsConstructor
#Entity
public class Model {
#Id #GeneratedValue private Integer id;
#NotBlank private String name;
private boolean flag;
}
In this case we implement the aspect like this:
#Aspect
#Component
public class ModelRepoAspect {
#Pointcut("execution(* com.example.ModelRepo.findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Pageable))")
public void modelFindAllWithPredicateAndPageable() {
}
#Around("modelFindAllWithPredicateAndPageable()")
public Object filterModelsByFlag(final ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
Predicate predicate = (Predicate) args[0];
BooleanExpression flagIsTrue = QModel.model.flag.eq(true);
if (predicate == null) {
args[0] = flagIsTrue;
} else {
if (!predicate.toString().contains("model.flag")) {
args[0] = flagIsTrue.and(predicate);
}
}
return pjp.proceed(args);
}
}
This aspect intercepts all calls of method findAll(Predicate predicate, Pageable pageable) of our repo, and add the filter model.flag = true to the query if the request parameters was not set (predicate == null), or if they don't contain 'flag' parameter. Otherwise aspect does not modify original predicate.

Spring Data REST custom query integration

I want to create a REST link for an Employee entity that will basically be a findByAllFields query. Of course this should be combined with Page and Sort. In order to do that I have implemented the following code:
#Entity
public class Employee extends Persistable<Long> {
#Column
private String firstName;
#Column
private String lastName;
#Column
private String age;
#Column
#Temporal(TemporalType.TIMESTAMP)
private Date hiringDate;
}
So I would like to have lets say a query where I can do:
http://localhost:8080/myApp/employees/search/all?firstName=me&lastName=self&ageFrom=20&ageTo=30&hiringDateFrom=12234433235
So I have the following Repository
#RepositoryRestResource(collectionResourceRel="employees", path="employees")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long>,
JpaSpecificationExecutor<Employee> {
}
Ok so now I need a RestController
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#RequestMapping(value = "/employees/search/all/search/all", method = RequestMethod.GET)
public Page<Employee> getEmployees(EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
return employeeRepository.findAll(specification, pageable);
}
Ok, obviously this does its job but it is not integrated with HATEOAS.
I have attempted to assemble a resource changing the controller to this:
public PagedResources<Resource<Employee>> getEmployees(
PagedResourcesAssembler<Employee> assembler,
EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return assembler.toResource(employees);
}
Obviously I'm missing something from the above since it doesnt work and I'm getting the following Exception:
Could not instantiate bean class [org.springframework.data.web.PagedResourcesAssembler]: No default constructor found;
Ok so to make the question clear I am trying to integrate the above resource into the rest of the HATEOAS architecture. I'm not entirely sure if this is the correct approach so any other suggestions are welcome.
EDIT:
Here you can see a similar implementation. Please take a look at the configuration, you will see that all but one of the "Person" controllers are working.
https://github.com/cgeo7/spring-rest-example
Try autowring PagedResourcesAssembler as a class member and change method signature something like below
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#Autowired
private PagedResourcesAssembler<Employee> pagedAssembler;
#RequestMapping(value = "/employees/search/all/search/all", method = RequestMethod.GET)
public ResponseEntity<Resources<Resource<Employee>>> getEmployees(EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return assembler.toResource(employees);
}
}
This works perfectly with Spring Data Rest 2.1.4.RELEASE
The code by #Stackee007 works but the resource won't include self links. In order to do that, a little more is required.
#Autowired
PagedResourcesAssembler<Appointment> pagedResourcesAssembler;
#RequestMapping(value = "/findTodaysSchedule")
public HttpEntity<PagedResources<Resource<Appointment>>> getTodaysSchedule(
PersistentEntityResourceAssembler entityAssembler, Pageable pageable) {
Page<Appointment> todaysSchedule = apptRepo.findByStartTimeBetween(beginningOfDay, endOfDay, pageable);
#SuppressWarnings({ "unchecked", "rawtypes" })
PagedResources<Resource<Appointment>> resource = pagedResourcesAssembler.toResource(todaysSchedule,
(ResourceAssembler) entityAssembler);
return new ResponseEntity<>(resource, HttpStatus.OK);
}
Spring HATEOAS has changed the name of Resource, PagedResources and some other classes. See here. Below is a working version in 2020.
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#Autowired
private PagedResourcesAssembler<Employee> pagedAssembler;
#RequestMapping(value = "/employees/search/all", method = RequestMethod.GET)
public ResponseEntity<PagedModel<EntityModel<Employee>>> getEmployees(PersistentEntityResourceAssembler entityAssembler,,
EmployeeCriteria filterCriteria,
Pageable pageable) {
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return ResponseEntity.ok(pagedAssembler.toModel(plants, (RepresentationModelAssembler) entityAssembler));
}
}

Resources