How to Implement Spring Boot Paging and multiple filtering using Criteria Api - spring

Today was my first time with criteria Api. i have create an application in Spring boot in order to make server side pagination with multiple key of an entity filtering.
So in my case i have created an entity called User and i started to implement code to paginate the data but with Criteria API.
After implementing the pagination without filtering and Criteria Api everything worked perfectly! every page return 8 results and it is well organized by current page, totalPages, etc ..
But later i have decided to start to implement Criteria API by searching my entity username and userRole. my goal is to make that paging i did in the last step mixed with filtering of keys.
In case that my keys are empty then paginate else paginate and filter.
So after implementing i have discouvered that filtering works perfectly but pagination do not work correctly anymore because i am receiving all the results in every page.
that problem happened only after implementing Criteria API which i just discovered today.
I am trying to reach my goal by keeping all i spoke about in one query and paginate correctly
Here what i have done with my UserCriteriaRepository
#Repository
public class UserCriteriaRepository {
private final EntityManager entityManager;
private final CriteriaBuilder criteriaBuilder;
public UserCriteriaRepository(EntityManager entityManager) {
this.entityManager = entityManager;
this.criteriaBuilder = entityManager.getCriteriaBuilder();
}
public ResponsePagingAndSorting<UserDTO> findAllWithFilters(int page, int size, String username, String userRole) {
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
Root<User> userRoot = criteriaQuery.from(User.class);
Predicate predicate = getPredicate(username,userRole, userRoot);
criteriaQuery.where(predicate);
TypedQuery<User> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setMaxResults(size * 10);
long usersCount = getUsersCount(predicate);
int totalPages = (int) ((usersCount / size) + 1);
List<User> userList = new ArrayList<>();
userList = typedQuery.getResultList();
List<UserDTO> userDTOList = UserMapper.toListDTO(userList);
return new ResponsePagingAndSorting<UserDTO>("Users List ",200,userDTOList,page,
usersCount, totalPages);
}
private Predicate getPredicate(String username, String userRole,
Root<User> userRoot) {
List<Predicate> predicates = new ArrayList<>();
if(Objects.nonNull(username)){
predicates.add(
criteriaBuilder.like(userRoot.get("username"),
"%" + username + "%")
);
}
if(Objects.nonNull(userRole)){
UserRoleType userRoleType = null;
switch (userRole){
case "MEMBER": userRoleType = UserRoleType.MEMBER;
break;
case "ADMIN": userRoleType = UserRoleType.ADMIN;
break;
case "SUPER_ADMIN": userRoleType = UserRoleType.SUPER_ADMIN;
break;
}
if (userRoleType != null) {
predicates.add(
criteriaBuilder.equal(userRoot.get("userRole"),
userRoleType)
);
}
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}
private Pageable getPageable(int page, int size) {
return PageRequest.of(page,size);
}
private long getUsersCount(Predicate predicate) {
CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
Root<User> countRoot = countQuery.from(User.class);
countQuery.select(criteriaBuilder.count(countRoot)).where(predicate);
return entityManager.createQuery(countQuery).getSingleResult();
}
}
My Service:
//paging with Criteria Api
#Override
public ResponsePagingAndSorting<UserDTO> getAllUsers(int page, int size ,String username, String userRole) {
ResponsePagingAndSorting<UserDTO> response = userCriteriaRepository.findAllWithFilters(page,size, username, userRole);
return response;
}
My Controller
#GetMapping("/get/all")
#ResponseBody
public ResponsePagingAndSorting<UserDTO> getAllUsers(#RequestParam(defaultValue = "0") int page,
#RequestParam(defaultValue = "8") int size,#RequestParam(defaultValue = "") String username,
#RequestParam(defaultValue = "") String userRole) {
ResponsePagingAndSorting<UserDTO> response = userService.getAllUsers(page,size,username,userRole);
log.warn("Response controller is " + response);
return response;
}
My ResponsePagingAndSorting dto object:
#AllArgsConstructor
#NoArgsConstructor
#Data
public class ResponsePagingAndSorting<T> {
String message;
int status_code;
List<T> body = new ArrayList<>();
int currentPage;
long totalItems;
int totalPages;
}
In Database i have in total of 17 users, so in postman i see all the 17 everytime but if i search by username or userRole or both it works? why pagination works only when i user the filters?
Can not i paginate data without seraching by username or userRole?
what is wrong with my code ???
how to make pagination works correctly with the filtering enabled or disabled?
Why if
Postman screen capture:
unfortunately all results are displayed in page 0
Screen Capture pagination + username filter: works correctly
i hope that i will find a solution

Problem Solved by using JpaSpecification
here the Specification class:
#Component
public class UserSpecification {
public Specification<User> getUsers(String username, String userRole) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (username != null && !username.isEmpty()) {
predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("username")),
"%" + username.toLowerCase() + "%"));
}
if (userRole != null && !userRole.isEmpty()) {
UserRoleType userRoleType = null;
switch (userRole) {
case "MEMBER": userRoleType = UserRoleType.MEMBER;
break;
case "ADMIN": userRoleType = UserRoleType.ADMIN;
break;
case "SUPER_ADMIN": userRoleType = UserRoleType.SUPER_ADMIN;
break;
}
predicates.add(criteriaBuilder.equal(root.get("userRole"), userRoleType));
}
query.orderBy(criteriaBuilder.asc(root.get("username")));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
}

Related

Paging and sorting over Collection

I want to apply paging and sorting to ArrayList of photos. The list is retrived by rest client. Here is my attempt to paging but returns all 5k elements instead. I try to achive a paging like in JpaRepository. The sorting is done by compareTo() method, and doesn't seem to work properly either.
PhotoServiceImpl.java
private List<Photo> repository;
public PhotoServiceImpl() {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<Photo>> photoResponse = restTemplate.exchange("https://jsonplaceholder.typicode.com/photos", HttpMethod.GET, null, new ParameterizedTypeReference<List<Photo>>() {
});
this.repository = photoResponse.getBody();
}
#Override
public List<Photo> findAll() {
return repository;
}
#Override
public Page<Photo> findAll(Pageable pageable) {
List<Photo> photos = findAll();
return new PageImpl<Photo>(photos, new PageRequest(pageable.getPageNumber(), pageable.getPageSize()), photos.size());
}
#Override
public Page<Photo> findAll(Pageable pageable, Sort.Direction sortOrder) {
List<Photo> photos = findAll();
return new PageImpl<Photo>(photos, new
PageRequest(pageable.getPageNumber(), pageable.getPageSize(), sortOrder), photos.size());
}
#Override
public List<Photo> findAll(Sort.Direction sortOrder) {
List<Photo> photos = repository
.stream()
.sorted()
.collect(Collectors.toList());
if (sortOrder.isDescending())
Collections.reverse(photos);
return photos;
}
Photo.java implements Comparable
private int id;
private int albumId;
private String title;
private URL url;
private URL thumbnailUrl;
#Override
public int compareTo(Object o) {
Photo out = ((Photo) o);
int c;
c = Integer.compare(this.getId(), out.getId());
if (c == 0)
c = Integer.compare(this.getAlbumId(), out.getAlbumId());
if (c == 0)
c = this.getTitle().compareTo((out.getTitle()));
return c;
}
}
Getting all photos and the wrapping that in a PageRequest instance with the page size and sorting set will not do what you want. The PageRequest class (or PageImpl class) does not perform the slicing of a list of data into a page or perform the sorting. You must do that yourself
List<Photo> photos = findAll();
//
// << Add your page extraction and sorting code here >>
//
return new PageImpl<Photo>(photos,
new PageRequest(pageable.getPageNumber(), pageable.getPageSize(), sortOrder), photos.size());

How to write a generic java function in Spring-boot for querying results,based on multiple query filters

I was working on java using jdo where I used to write query functions like below, which queries from an Entity based on what parameters are passed to the function.
Now Im moving to spring-boot, and want to know if I can achieve the same using spring-boot.Any help or suggestions would be heartfully appreciated.Thank you!!
public List<Result> getQueryResult(int filter1, String filter2,Float filter3,Long id){
Query query = new Query("select from Entity1");
String filter = "id == "+id;
if(filter1 != null){
filter = filter+" && filter1 == "+filter1+";
}
if(filter2 != null){
filter = filter+" && filter2 == '"+filter2+"'";
}
if(filter3 != null){
filter = filter+"filter3 == "+filter3;
}
query.setFIlter(filter);
List<Result> results = query.excute();
return results;
}
You have two options - you can use JPA Criteria Builder or JPA Specifications
class Person {
String firstName;
String lastName;
int age;
}
JPA Criteria Builder
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
Predicate sellAlcohol = builder.ge(root.get(Person_.age), 21);
Predicate toMindy = builder.equal(root.get(Person_.firstName), "Mindy");
Usage
query.where(builder.and(sellAlcohol, toMindy));
em.createQuery(query.select(root)).getResultList();
Specificatons
public PersonSpecifications {
public static Specification<Person> sellAlcohol() {
return new Specification<Person> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.ge(root.get(Person_.age), 21);
}
};
}
public static Specification<Person> toMindy() {
return new Specification<Person> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.equal(root.get(Person_.firstName), "Mindy");
}
};
}
}
Usage
personRepository.findAll(where(sellAlcohol()).and(toMindy()));

spring jpa query with pageable, sort and filter and return projection

I am using Spring Data Rest with org.springframework.boot 1.5.2 with hibernate 5.2.9. What i am trying to achieve is a way to use JPA to query with sort, filter, pageable that can return a subset of the entity or return a projection.
Below is the code that uses:
(1) Specification for filtering
(2) Projection and Excerpts to apply projection in collection
(3) The controller that tries to return Page,
but it only works if the return type is Page.
where Student is the entity, StudentLite is the projection
Question is:
(1) How to have a query+sort+filter that returns Page projection
(2) Possible to apply the Excerpts to just that query?
(3) Any way to use #JsonView in #RepositoryRestController to solve?
StudentRepository class
#RepositoryRestResource(excerptProjection = StudentLite.class)
public interface StudentRepository extends PagingAndSortingRepository<Student,Long>,
JpaSpecificationExecutor<Student> {}
and
StudentSpecification class
public class StudentSpecification {
public static Specification<Student> filteredStudentList(StudentSearch c) {
final StudentSearch criteria = c;
return new Specification<Student>() {
#Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Join<Student, Contact> joinContact = root.join(Student_.contact);
Path<Contact> contact = root.get(Student_.contact);
Path<String> officialId = root.get(Student_.officialId);
Path<String> name = root.get(Student_.name);
Path<String> email = contact.get(Contact_.email);
Path<String> phoneMobile = contact.get(Contact_.phoneMobile);
final List<Predicate> predicates = new ArrayList<Predicate>();
if(criteria.getOfficialId()!=null) {
predicates.add(cb.like(officialId, "%" + criteria.getOfficialId() + "%"));
System.out.println("==not null...criteria.getOfficialId()="+criteria.getOfficialId()+" :officialId="+officialId.toString());
}
if(criteria.getName()!=null) {
predicates.add(cb.like(name, "%"+criteria.getName()+"%"));
}
if(criteria.getEmail()!=null) {
predicates.add(cb.like(email, "%"+criteria.getEmail()+"%"));
}
if(criteria.getPhoneMobile()!=null) {
predicates.add(cb.like(phoneMobile, "%"+criteria.getPhoneMobile()+"%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
}
and the controller where the class is annotated with #ExposesResourceFor(Student.class) and #RepositoryRestController :
#RequestMapping(method=RequestMethod.GET)
public #ResponseBody Page<StudentLite> getStudentList(Pageable pageable, #RequestParam Map<String,String> criteria) {
StudentSearch ss = new StudentSearch(criteria);
// Below statement fail, as findAll(...) is suppose to return Page<Student>
Page<StudentLite> pagedStudentLite = studentRep.findAll( StudentSpecification.filteredStudentList(ss), pageable);
return pagedStudentLite;
}

Java: GroupSequenceProvider for Validation, object is null in getValidationGroups method

This is what I am trying to achieve:
I have an update request object and user is allowed to do Partial Updates. But I want to validate the field only if it is in the request body. Otherwise, it is OK to be null. To achieve this, I am using GroupSequenceProvider to let the Validator know what groups to validate. What am I doing wrong here? If there is a blunder, how do I fix it?
Documentation: https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-groups.html#example-implementing-using-default-group-sequence-provider
#GroupSequenceProvider(UpdateUserRegistrationGroupSequenceProvider.class)
public class UpdateUserRegistrationRequestV1 {
#NotBlank(groups = {EmailExistsInRequest.class})
#Email(groups = {EmailExistsInRequest.class})
#SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, groups = {EmailExistsInRequest.class})
private String email;
#NotNull(groups = {PasswordExistsInRequest.class})
#Size(min = 8, max = 255, groups = {PasswordExistsInRequest.class})
private String password;
#NotNull(groups = {FirstNameExistsInRequest.class})
#Size(max = 255, groups = {FirstNameExistsInRequest.class})
#SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, groups = {FirstNameExistsInRequest.class})
private String firstName;
// THERE ARE GETTERS AND SETTERS BELOW
}
Group Sequence Provider Code:
public class UpdateUserRegistrationGroupSequenceProvider implements DefaultGroupSequenceProvider<UpdateUserRegistrationRequestV1> {
public interface EmailExistsInRequest {}
public interface PasswordExistsInRequest {}
public interface FirstNameExistsInRequest {}
#Override
public List<Class<?>> getValidationGroups(UpdateUserRegistrationRequestV1 updateUserRegistrationRequestV1) {
List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();
defaultGroupSequence.add(Default.class);
defaultGroupSequence.add(UpdateUserRegistrationRequestV1.class);
if(StringUtils.hasText(updateUserRegistrationRequestV1.getEmail())) {
defaultGroupSequence.add(EmailExistsInRequest.class);
}
if(StringUtils.hasText(updateUserRegistrationRequestV1.getPassword())) {
defaultGroupSequence.add(PasswordExistsInRequest.class);
}
if(StringUtils.hasText(updateUserRegistrationRequestV1.getFirstName())) {
defaultGroupSequence.add(FirstNameExistsInRequest.class);
}
return defaultGroupSequence;
}
}
I am using Spring MVC, so this is how my controller method looks,
#RequestMapping(value = "/{userId}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void updateUser(#PathVariable("userId") Long userId,
#RequestBody #Valid UpdateUserRegistrationRequestV1 request) {
logger.info("Received update request = " + request + " for userId = " + userId);
registrationService.updateUser(userId, conversionService.convert(request, User.class));
}
Now the problem is, the parameter "updateUserRegistrationRequestV1" in the UpdateUserRegistrationGroupSequenceProvider.getValidationGroups method is null. This is the request object that I am sending in the request body and I am sending email field with it.
What am I doing wrong?
I too went through the same issue ,and hopefully solved it
You just have to check the object is null and put all your conditions inside it.
public List<Class<?>> getValidationGroups(Employee object) {
List<Class<?>> sequence = new ArrayList<>();
//first check if the object is null
if(object != null ){
if (!object.isDraft()) {
sequence.add(Second.class);
}
}
// Apply all validation rules from default group
sequence.add(Employee.class);
return sequence;
}

spring data neo4j crud - many optional param?

I use Spring-data-neo4j with one CrudRepository
#Repository
public interface PersonRepository extends GraphRepository<Person> {}
I have a Html form with 3 inputs FirstName, Name, Age, so I have possible a multiple criteria choose : All, FirstName, FirstName + Name, FirstName + Age etc....
I would like to make a "multiple criteria find" with Map or other stuff. Is it possible?
I try this in my CRUD:
List<Person> findByFirstnameAndNameAndAge(String firstname, String name, int age);
but it's not work if one or all parameters is null.
Try to use a map and a #Query annotation
#Query("MATCH (u:Person) WHERE u.name = {param}.name OR u.age = {param}.age RETURN u")
List<Person> findDynamic(#Param("param") Map params);
Hi #Michael Hunger Thank you for your response. It's not exactly what I expected but you delivered me some fine search stuff
finally I do this :
import org.apache.commons.collections.map.HashedMap;
import com.google.common.collect.Lists;
(...)
#Autowired
private EventRepository eventRepository; //#Repository extends GraphRepository<Event>
(...)
public List<Event> findByDynamicParam(HashedMap params) {
String query = "match (event)-[:user]-(user), (event)-[:action]-(action)";
if (!params.isEmpty()) {
query += " where";
}
if (params.containsKey("actionId")) {
query += " id(action) = {actionId} and";
}
if (params.containsKey("userId")) {
query += " id(user) = {userId} and";
}
if (!params.isEmpty()) {
query = query.substring(0, query.length() - 4);
}
query += " return (event)";
return Lists.newArrayList(eventRepository.query(query, params));
}
client's caller :
HashedMap params = new HashedMap();
if (actionId != null) {
params.put("actionId", actionId);
}
if (userId != null) {
params.put("actionId", userId);
}
List<Event> events = eventService.findByDynamicParam(params);
What do you think? Is it possible to optimize this function?
Regards
Olivier from Paris

Resources