2

I have following requestDto and controller

@PostMapping 
public responseDto search(@RequestBody SearchUserRequestDto request){
    userService.search(request);
}
public class SearchUserRequestDto {

    private Long userId;
    private String firstName;
    private String lastName;
    private String role;
    private String userType;
}

I want to search the user repository with the fields that are not null in above Dto.

Example: If firstName is "john" and lastName is "smith" and rest of the fields are null, then I have to search the repository for all records WHERE firstName is "john" AND lastName is "smith"(Not OR)

I also have to implement pagination for the response

I am new to spring-boot can anyone suggest me how to do it?

harv3
  • 263
  • 1
  • 6
  • 19
  • 1
    For such kind of tasks it's commonly using Criteria or QueryDSL, for example https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/, https://stackoverflow.com/questions/20280708/filtering-database-rows-with-spring-data-jpa-and-spring-mvc – MarkHuntDev Jan 10 '20 at 09:02

2 Answers2

3

As suggested by @mandar-dharurkar you can use org.springframework.data.domain.Pageable but for conditional search you use something like below,

@Query("SELECT u FROM User u WHERE (:name is null or u.name = :name) and (:lastname is null"
  null + " or u.lastname= :lastname)")
Page<User> search(@Param("name") String name, @Param("lastname") String lastname, Pageable pageable);
Jaganath Kamble
  • 506
  • 2
  • 10
2

If you are using JPA, I recommend using Spring Data JPA and the Specification API. When I originally looked at Spring Boot and Spring Data, the only real approach I saw promoted was Spring Data Rest which was too much magic for my taste.

I personally like to have Controllers invoke methods on services (reusable business logic layer that's basically the Facade pattern). These services eventually call Spring Data repositories.

Consider a User object

public class User{
  String firstName;
  String lastName;
}

Let's say we have a UserController handling our REST requests for the User resource. This controller converts a Request with UserSearchCriteria to a Spring Data Page<User>

import org.springframework.data.domain.Page;

public class UserController{

  @Autowired
  UserService userService;

  @RequestMapping(path = "/users", method = RequestMethod.GET)
  Page<User> getAll(HttpServletRequest request, UserSearchCriteria searchCriteria){
     return userService.findAllUsers(searchCriteria);
  }
}

The UserSearchCriteria are the types of search params your client would pass as query params like GET /users?firstName=mista&lastName=henry which get automapped to UserSearchCriteria fields.

import org.springframework.data.domain.Sort;
import org.springframework.data.domain.PageRequest;
public class UserSearchCriteria {
    UserSearchCriteria(){
        super();
        sort = "lastName";
    }
    Integer size;
    Integer page;
    String sort;
    String sortDir;

    String firstName;
    String lastName;
    Sort buildSort(){
        return new Sort(new Sort.Order(Sort.Direction.ASC, sort).ignoreCase());
    }
    PageRequest toPageRequest(){
        if(size == null){
            size = Integer.MAX_VALUE; // may or may not be a good idea for your usecase
        }
        return new PageRequest(page, size, buildSort());
    }
}

PageRequest and Sort are part of the Spring Data project. In my projects, I let the sorting and paging existing in an AbstractSearchCriteria object for easier reuse which all of my search criterias extend, but it's simpler to demonstrate as above.

In the UserService layer, I will delegate to my repository (I can also check access requirements, set defaults, etc.

import org.springframework.data.domain.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@Transactional
public class UserService{

  @Autowired
  UserRepository userRepository;

  Page<User> findAllUsers(UserSearchCriteria userSearchCriteria){
    if(userSearchCriteria == null){
      userSearchCriteria = new UserSearchCriteria();
    }
    return userRepository.findAll(UserSearchSpecification.findByCriteria(userSearchCriteria), userSearchCriteria.toPageRequest());
  }
}

The UserSearchSpecification uses the Specification API to dynamically add searches to your JPA call, which I find cleaner than using the Criteria API directly.

import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
public class UserSearchSpecification{

  public static Specification<User> findByCriteria(final UserSearchCriteria searchCriteria){
    return new Specification<User>() {

      @Override
      Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> predicates = new ArrayList<Predicate>();

        // if firstName in criteria, do an uppercase prefix match
        if(searchCriteria.firstName != null){
          predicates.add(
            cb.like(
              cb.upper(root.get("firstName")),
              "%" + searchCriteria.lastName.toUpperCase()
            )
          );
        }

        // if lastName in criteria, do an uppercase prefix match
        if(searchCriteria.lastName != null){
          predicates.add(
            cb.like(
              cb.upper(root.get("lastName")),
              "%" + searchCriteria.lastName.toUpperCase()
            )
          );
        }
        if(predicates.size() > 0){
          return cb.and(predicates.toArray());
        }else{
          return null;
        }
      }
    }
  }
}

This allows you to easily inspect the passed in params from the rest call (properties on the UserSearchSpecification) and dynamically build up the restrictions for the database call. At the end, I chose to and together each predicate, but you can do whatever you want here. You can also check for equality instead of like, greater/less than, etc.

Lastly, notice that this specification is passed to the UserRepository in the UserService.findAllUsers method. This is a Spring Data repository:

import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface UserRepository extends PagingAndSortingRepository<User, Long>, JpaSpecificationExecutor<User>{

}

In Spring Data, you often only need to extend certain repositories in an interface like here. Spring Data can handle the rest under the hood. It's important to note the extension of PagingAndSortingRepository which handles the paging and sorting aspect as well as the JpaSpecificationExecutor which takes Specification objects and generates the correct query.

This may seem like a lot of code, but this has scaled really well for me as the codebase grows. Once you have the specification in place, you can easily add new fields to your SearchCriteria object and add new restrictions in the Specification by inspecting the new fields and creating Predicate.

mistahenry
  • 8,554
  • 3
  • 27
  • 38
  • 1
    Thanks. From your answer and link provided by @MarkHunt in comments I was able to do it and it is working – harv3 Jan 13 '20 at 12:37