spring boot find by field search specification - java

My problem is I can search from database. But I did searching by using findAll of JpaSpecificationExecutor. But, I want to do searching by using findById and pass my specification, pageable and id to it return page. But it is not working.
Here is my controller:
#GetMapping(value = "/search")
public ResponseEntity<ResponseDTO> allAccountRightService(
#RequestParam(value = "search", required = false) String search,
#RequestParam(value = "page", required = false) Integer page,
#RequestParam(value = "size", required = false) Integer size,
#RequestParam(value = "order", required = false) String order,
#RequestParam(value = "orderBy", required = false) String orderBy) {
ResponseDTO responseDTO = new ResponseDTO("accountRightService List", accountRightService.search(search, page, size, order, orderBy));
return new ResponseEntity<>(responseDTO, HttpStatus.OK);
}
and here is my `service impl` method:
public Map<PageInformation, List<AccountRightDTO>> search(String search, Integer page, Integer size, String order,
String orderBy) {
Map<PageInformation, List<AccountRightDTO>> accountRightList = new HashMap<>();
PageInformation pageInfo = new PageInformation();
if (order == null || order.isEmpty())
order = "DESC";
if (orderBy == null || orderBy.isEmpty())
orderBy = "createdAt";
Pageable pageable = CommonUtil.createPageRequest(page, size, order, orderBy);
Specification<AccountRight> spec = CommonUtil.buildSearchSpecification(search);
//Page<AccountRight> accountRightPage = accountRightRepository.findAllByRightByAppointment(CommonUtil.getAppointment().getAppointmentID(), spec, pageable);
Page<AccountRight> accountRightPage = accountRightRepository.findAll(spec, pageable);
List<AccountRight> accountRights = accountRightPage.getContent();
List<AccountRightDTO> accountRightDTOs = new ArrayList<>();
accountRightDTOs = accountRights.stream().map(accountRight -> {
AccountRightDTO accountRightDTO = new AccountRightDTO();
AppointmentDTO rightToAppointmentDTO = new AppointmentDTO();
AppointmentDTO rightByAppointmentDTO = new AppointmentDTO();
BeanUtils.copyProperties(accountRight, accountRightDTO, "accountRightID");
accountRightDTO.setAccountRightID(Long.toString(accountRight.getAccountRightID()));
BeanUtils.copyProperties(accountRight.getRightToAppointment(), rightToAppointmentDTO, "appointmentID");
rightToAppointmentDTO.setAppointmentID(Long.toString(accountRight.getRightToAppointment().getAppointmentID()));
BeanUtils.copyProperties(accountRight.getRightByAppointment(), rightByAppointmentDTO, "appointmentID");
rightByAppointmentDTO.setAppointmentID(Long.toString(accountRight.getRightToAppointment().getAppointmentID()));
accountRightDTO.setRightByAppointment(rightByAppointmentDTO);
accountRightDTO.setRightToAppointment(rightToAppointmentDTO);
return accountRightDTO;
}).collect(Collectors.toList());
pageInfo.setSize(accountRightPage.getSize());
pageInfo.setTotalElements(accountRightPage.getTotalElements());
pageInfo.setTotalPages(accountRightPage.getTotalPages());
accountRightList.put(pageInfo, accountRightDTOs);
return accountRightList;
}
and this is my buildsearchspecification method
public static <T> Specification<T> buildSearchSpecification(String search) {
SearchSpecificationsBuilder<T> builder = new SearchSpecificationsBuilder<T>();
if (search != null && !search.isEmpty()) {
String[] str = search.split(",");
if (str != null) {
for (String strTemp : str) {
Pattern pattern = Pattern.compile("(\\p{Punct}?)(.*)(:|!|<|>|~)(.*)(\\p{Punct}?),");
Matcher matcher = pattern.matcher(strTemp + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2),
SearchOperation.getSimpleOperation(matcher.group(3).toCharArray()[0]),
matcher.group(4));
}
}
}
}
Specification<T> spec = builder.build();
return spec;
}
and here is my findAllByRightByAppointment repository method
#Query("select account from AccountRight account where account.rightByAppointment.appointmentID=?1")
Page<AccountRight> findAllByRightByAppointment(Long appointmentID, #Nullable Specification<AccountRight> spec, Pageable pageable);
If I use findAll method than searching will work otherwise by using my custom method pagination works without searching

I found an answer by using Specification.Where(your_specification).and(your_search_specification).
here is my updated code now:
Specification<AccountRight> searchSpec = CommonUtil.buildSearchSpecification(search); //this specification needs my search string.
SearchSpecification<AccountRight> rightByAppointmentID =
new SearchSpecification<AccountRight>(new SearchCriteria("rightByAppointment.appointmentID", SearchOperation.EQUALITY, CommonUtil.getAppointment().getAppointmentID())); //this specification accepts search criteria with key, operation and value.
Page<AccountRight> accountRightPage = accountRightRepository.findAll(Specification.where(rightByAppointmentID).and(searchSpec), pageable);
//here you will just tell findAll method to findAll entities where rightByAppointmentID is equal to
//CommonUtil.getAppointment().getAppointmentID() and search query is searchSpec

Related

JUnit ServiceTest for getAll method which contains Pageable, Page object and stream()

I am new to Junit test and now have no idea about getAll method if I use Pageable, Page and stream() method in my service class.
Here is my getAll method of PostServiceImpl.class
#Override
public PostResponse getAllPosts(Integer pageNo, Integer pageSize, String sortBy, String sortDirection) {
Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name())
? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending();
Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort);
Page<Post> page = postRepository.findAll(pageable);
List<Post> posts = page.getContent();
List<PostDto> content = posts.stream()
.map(post -> postConverter.mapToDto(post))
.collect(Collectors.toList());
PostResponse postResponse = new PostResponse();
postResponse.setContent(content);
postResponse.setPageNo(page.getNumber() + 1);
postResponse.setPageSize(page.getSize());
postResponse.setTotalElements(page.getTotalElements());
postResponse.setTotalPages(page.getTotalPages());
postResponse.setLast(page.isLast());
return postResponse;
}
PostResponse class
public class PostResponse {
private List<PostDto> content;
private Integer pageNo;
private Integer pageSize;
private Long totalElements;
private Integer totalPages;
private Boolean last;
}
my getAll method of ServiceTes.class and it seems that I should not write Page<Post> page = postRepository.findAll(pageable); in my test case and therefore I do not know how to pass my pageable object in the findAll() method to get the page object then proceed this test case. Please help me and thank you in advance.
#BeforeEach
public void setup() {
postDto = new PostDto();
postDto.setId(1L);
postDto.setTitle("test title");
postDto.setDescription("test description");
postDto.setContent("test content");
post = new Post();
post.setId(1L);
post.setTitle("test title");
post.setDescription("test description");
post.setContent("test content");
}
#Test
void givenPostList_whenGetAllPosts_thenReturnPostResponse() {
int pageNo = 1;
int pageSize = 3;
String sortBy = "title";
String sortDirection = "ASC";
Post post1 = new Post();
post1.setId(2L);
post1.setTitle("test title2");
post1.setDescription("test description2");
post1.setContent("test content2");
List<Post> postList = List.of(post, post1);
Sort sort = Sort.by(sortBy).ascending();
Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort);
BDDMockito.given(postRepository.findAll())
.willReturn(postList); // not sure if it is necessary
// do not know what code should be here (page and stream method)
PostResponse postResponse = postService.getAllPosts(pageNo, pageSize, sortBy, sortDirection);
Assertions.assertThat(postResponse).isNotNull();
}
You are returning List<Post> here
BDDMockito.given(postRepository.findAll()).willReturn(postList);
when it is expecting Page<Post> here
Page<Post> page = postRepository.findAll(pageable);
Create a Page Object with that list object and return that. Something like
Page<Post> postPage = new PageImpl<>(postList);
BDDMockito.given(postRepository.findAll()).willReturn(postPage);
I think you might need to go through the documentation a bit more. I would suggest https://www.baeldung.com/mockito-series

Spring Boot, Hibernate, Querydsl: antlr.NoViableAltException: unexpected token

I am currently developing a data warehouse with spring boot, hibernate and querydsl.
Nearly everything is working fine, but I got trouble doing a search request for one of my entity called group. The errors, are not really helpful:
My request is simple /group/advancedSearch?page=0&size=10&sort=name,asc&search=groupCode:dfa,name:dfa,
The errors raise in my service when I do call the repository method.
antlr.NoViableAltException: unexpected token: group
[...]
java.lang.NullPointerException: null
To make this more understandable my code is below. I have the same method for most of my entities and there it is working fine. Because I had no clue where the unexpected token group might come from, I had a look at the generated class QGroup, there I found this peace of code public static final QGroup group = new QGroup("group1");. The name group1 made me wonder, but I'm not sure if it has anything to do with the errors. In all other classes the string was always the name of the class with initial letters small.
I thought the entity group might be duplicated, so querydsl would create group and group1, but that's not the case. So any ideas where the errors might come from and how to prevent / fix them?
The entity:
#Entity
#Table(name = "[Group]")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Group_ID")
private long groupId;
#ManyToOne()
#JoinColumn(name = "Dimension_ID")
private Dimension dimension;
#Column(name = "Dimension_ID", updatable = false, insertable = false)
private Long dimensionId;
#Column(name = "GroupCode")
private String groupCode;
#Column(name = "Name")
private String name;
[...]
}
The function of the controller where the errors raise:
#RequestMapping(value = GROUP_URL + "/advancedSearch", method = RequestMethod.GET)
#ResponseBody
public PagedResources<Group> advancedSearch(
#RequestParam(value = "search", required = false) String search,
Pageable pageable, #RequestParam MultiValueMap<String, String> parameters,
PersistentEntityResourceAssembler persistentEntityResourceAssembler
) {
SimpleGrantedAuthority[] allowedRoles = {SYSADMIN};
GeneralPredicateBuilder<Group> builder = new GeneralPredicateBuilder<>(Group.class);
Predicate predicate = predicateService.getPredicateFromParameters(parameters, Group.class);
Page<Group> results = service.advancedSearch(
this.buildAdvancedSearch(search, predicate, builder), pageable, allowedRoles);
return super.toPagedResource(results, persistentEntityResourceAssembler);
}
public Predicate buildAdvancedSearch(String search, Predicate predicate, GeneralPredicateBuilder<T> builder) {
if (search != null) {
Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
}
BooleanExpression expression = builder.build();
if (predicate != null) {
predicate = expression.and(predicate);
} else {
predicate = expression;
}
}
return predicate;
}
The PredicateService:
#Service
public class PredicateService {
#Autowired
private final QuerydslPredicateBuilder querydslPredicateBuilder;
#Autowired
private final QuerydslBindingsFactory querydslBindingsFactory;
public PredicateService(QuerydslPredicateBuilder querydslPredicateBuilder, QuerydslBindingsFactory querydslBindingsFactory) {
this.querydslPredicateBuilder = querydslPredicateBuilder;
this.querydslBindingsFactory = querydslBindingsFactory;
}
public <T> Predicate getPredicateFromParameters(final MultiValueMap<String, String> parameters, Class<T> tClass) {
TypeInformation<T> typeInformation = ClassTypeInformation.from(tClass);
return querydslPredicateBuilder.getPredicate(typeInformation, parameters, querydslBindingsFactory.createBindingsFor(typeInformation));
}
}
The service method:
public Page<Group> advancedSearch(Predicate predicate, Pageable pageable, SimpleGrantedAuthority[] roles){
if (SecurityUtils.userHasAnyRole(roles)) {
return this.repository.findAll(predicate, pageable); // <-- here the errors raise
} else throw new ForbiddenException(FORBIDDEN);
}
The repository:
#RepositoryRestResource(collectionResourceRel = GROUP_URL, path = GROUP_URL)
#CrossOrigin(exposedHeaders = "Access-Control-Allow-Origin")
public interface GroupRepository extends PagingAndSortingRepository<Group, Long>, JpaSpecificationExecutor<Group>, QuerydslPredicateExecutor<Group> {
}
The generated class QGroup by querydsl:
#Generated("com.querydsl.codegen.EntitySerializer")
public class QGroup extends EntityPathBase<Group> {
private static final long serialVersionUID = 384278695L;
private static final PathInits INITS = PathInits.DIRECT2;
public static final QGroup group = new QGroup("group1"); // <-- this is confusing
[...]
Update:
I finally found the generated query:
select group1
from Group group1
where ?1 = ?1 and lower(group.groupCode) like ?2 escape '!'
I think here is the problem. Form a SQL developer view, group.groupCode should be group1.groupCode. Anyone knows how to fix this?
Update 2 [2020-02-14]:
The GeneralPredicateBuilder:
public class GeneralPredicateBuilder<T> {
private List<SearchCriteria> params;
private final Class<T> type;
public GeneralPredicateBuilder(Class<T> type) {
this.params = new ArrayList<>();
this.type = type;
}
public GeneralPredicateBuilder<T> with(String key, String operation, Object value) {
params.add(new SearchCriteria(key, operation, value));
return this;
}
public BooleanExpression build() {
if (params.size() == 0) {
return null;
}
List<BooleanExpression> predicates = params.stream().map(param -> {
GeneralPredicate<T> predicate = new GeneralPredicate<T>(param, type);
BooleanExpression tmp = predicate.getPredicate();
return tmp;
}).filter(Objects::nonNull).collect(Collectors.toList());
BooleanExpression result = Expressions.asBoolean(true).isTrue();
for (BooleanExpression predicate : predicates) {
result = result.and(predicate);
}
return result;
}
public List<Predicate> buildPredicate(){
if (params.size() == 0) {
return null;
}
return params.stream().map(param -> {
GeneralPredicate<T> predicate = new GeneralPredicate<>(param, type);
return predicate.getPredicate();
}).filter(Objects::isNull).collect(Collectors.toList());
}
}
I still don't understand why the, by querydsl, generated classname of Group is group1, but in combination with my GenericPredicateBuilder and the GenericPredicate this leads to the inconsistent sql, as shown in the question. But I was finally able to fix this, unfortunately in a really dirty way. For completeness here is my GeneralPredicate:
public class GeneralPredicate<T> {
private SearchCriteria searchCriteria;
private final Class<T> type;
private final String variable;
public GeneralPredicate(SearchCriteria param, Class<T> type) {
searchCriteria = param;
this.type = type;
if(type.getSimpleName().equals("Group")){
this.variable = "group1";
} else {
this.variable = type.getSimpleName().replaceFirst("" + type.getSimpleName().charAt(0), "" + type.getSimpleName().charAt(0)).toLowerCase();
}
}
public BooleanExpression getPredicate() {
PathBuilder<T> entityPath = new PathBuilder<T>(type, variable);
if (isNumeric(searchCriteria.getValue().toString())) {
NumberPath<Integer> path = entityPath.getNumber(searchCriteria.getKey(), Integer.class);
int value = Integer.parseInt(searchCriteria.getValue().toString());
switch (searchCriteria.getOperation()) {
case ":":
return path.eq(value);
case ">":
return path.goe(value);
case "<":
return path.loe(value);
}
} else {
StringPath path = entityPath.getString(searchCriteria.getKey());
switch (searchCriteria.getOperation()) {
case ":":
return path.containsIgnoreCase(searchCriteria.getValue().toString());
case "<":
return path.startsWith(searchCriteria.getValue().toString());
case ">":
return path.endsWith(searchCriteria.getValue().toString());
}
}
return null;
}
}
You find the dirty fix within the constructor. I really hate it, but it is working and the generated sql is okay.
Maybe I use the generic in a wrong way. I am open for advices.

How to access data from RestTemplate as List

I want to access data from a spring boot service. The return type of the data is a List, but every time I access it, the list is empty.
This is my code:
Map<String, String> params = new HashMap<String, String>();
params.put("firstName", "test" );
params.put("lastName", "test1");
ResponseEntity<Person[]> response = restTemplate.getForEntity(url, Person[].class, params);
In this case, response.getBody() is an empty [].
#RequestMapping(value = "/search", method = RequestMethod.GET)
public List<Person> searchUsers(
#RequestParam(value = "firstName", required = false) String firstName,
#RequestParam(value = "lastName", required = false) String lastName,
#RequestParam(value = "email", required = false) String email {
return personService.search(firstName, lastName, email, company);
}
I also tried with String, and Person[], but nothing worked.
Thanks in advance!
#GET
#Path("statement")
#Produces({MediaType.APPLICATION_XML})
public Response statement(#QueryParam("from") String from, #QueryParam("to") String to) {
DB idb = new DB();
List<Transaction> transactions = idb.getTransactionsByDate(from, to);
final GenericEntity<List<Transaction>> entity = new GenericEntity<List<Transaction>>(transactions) {
};
return Response.status(Response.Status.OK).entity(entity).build();
}

spring-data-mongodb optional query parameter

I am using spring-data-mongodb.
I want to query database by passing some optional parameter in my query.
I have a domain class.
public class Doc {
#Id
private String id;
private String type;
private String name;
private int index;
private String data;
private String description;
private String key;
private String username;
// getter & setter
}
My controller:
#RequestMapping(value = "/getByCategory", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public Iterable<Doc> getByCategory(
#RequestParam(value = "key", required = false) String key,
#RequestParam(value = "username", required = false) String username,
#RequestParam(value = "page", required = false, defaultValue = "0") int page,
#RequestParam(value = "size", required = false, defaultValue = "0") int size,
#RequestParam(value = "categories") List<String> categories)
throws EntityNotFoundException {
Iterable<Doc> nodes = docService.getByCategory(key, username , categories, page, size);
return nodes;
}
Here Key and username are optional query parameters.
If I pass any one of them it should return the matching document with given key or username.
My service method is:
public Iterable<Doc> getByCategory(String key, String username, List<String> categories, int page, int size) {
return repository.findByCategories(key, username, categories, new PageRequest(page, size));
}
Repository:
#Query("{ $or : [ {'key':?0},{'username':?1},{categories:{$in: ?2}}] }")
List<Doc> findByCategories(String key, String username,List<String> categories, Pageable pageable);
But by using above query it does not returns a document with either given key or username.
What is wrong in my query?
This is how I am making request
http://localhost:8080/document/getByCategory?key=key_one&username=ppotdar&categories=category1&categories=category2
Personally, I'd ditch the interface-driven repository pattern at that point, create a DAO that #Autowires a MongoTemplate object, and then query the DB using a Criteria instead. that way, you have clear code that isn't stretching the capabilities of the #Query annotation.
So, something like this (untested, pseudo-code):
#Repository
public class DocDAOImpl implements DocDAO {
#Autowired private MongoTemplate mongoTemplate;
public Page<Doc> findByCategories(UserRequest request, Pageable pageable){
//Go through user request and make a criteria here
Criteria c = Criteria.where("foo").is(bar).and("x").is(y);
Query q = new Query(c);
Long count = mongoTemplate.count(q);
// Following can be refactored into another method, given the Query and the Pageable.
q.with(sort); //Build the sort from the pageable.
q.limit(limit); //Build this from the pageable too
List<Doc> results = mongoTemplate.find(q, Doc.class);
return makePage(results, pageable, count);
}
...
}
I know this flies in the face of the trend towards runtime generation of DB code, but to my mind, it's still the best approach for more challenging DB operations, because it's loads easier to see what's actually going on.
Filtering out parts of the query depending on the input value is not directly supported. Nevertheless it can be done using #Query the $and operator and a bit of SpEL.
interface Repo extends CrudRepository<Doc,...> {
#Query("""
{ $and : [
?#{T(com.example.Repo.QueryUtil).ifPresent([0], 'key')},
?#{T(com.example.Repo.QueryUtil).ifPresent([1], 'username')},
...
]}
""")
List<Doc> findByKeyAndUsername(#Nullable String key, #Nullable String username, ...)
class QueryUtil {
public static Document ifPresent(Object value, String property) {
if(value == null) {
return new Document("$expr", true); // always true
}
return new Document(property, value); // eq match
}
}
// ...
}
Instead of addressing the target function via the T(...) Type expression writing an EvaluationContextExtension (see: json spel for details) allows to get rid of repeating the type name over and over again.

Handle optional parameters in QueryDSL

I am using QueryDSL with SpringData.
I have Table say, Employee and I have created entity class say, EmployeeEntity
I have written following service method
public EmployeeEntity getEmployees(String firstName, String lastName)
{
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression query = null;
if(firstName != null)
{
query = employee.firstName.eq(firstName);
}
if(lastName != null)
{
query = query.and(employee.lastName.eq(lastName)); // NPException if firstName is null as query will be NULL
}
return empployeeDAO.findAll(query);
}
As in above I commented the NPException. How to use QueryDSL for optional Parameters in QueryDSL using Spring Data?
Thank you :)
BooleanBuilder can be used as a dynamic builder for boolean expressions:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanBuilder where = new BooleanBuilder();
if (firstName != null) {
where.and(employee.firstName.eq(firstName));
}
if (lastName != null) {
where.and(employee.lastName.eq(lastName));
}
return empployeeDAO.findAll(where);
}
BooleanBuilder is good. You can also wrap it and add "optional" methods in order to avoid the if conditions:
For example, for "and" you can write: (Java 8 lambdas are used)
public class WhereClauseBuilder implements Predicate, Cloneable
{
private BooleanBuilder delegate;
public WhereClauseBuilder()
{
this.delegate = new BooleanBuilder();
}
public WhereClauseBuilder(Predicate pPredicate)
{
this.delegate = new BooleanBuilder(pPredicate);
}
public WhereClauseBuilder and(Predicate right)
{
return new WhereClauseBuilder(delegate.and(right));
}
public <V> WhereClauseBuilder optionalAnd(#Nullable V pValue, LazyBooleanExpression pBooleanExpression)
{
return applyIfNotNull(pValue, this::and, pBooleanExpression);
}
private <V> WhereClauseBuilder applyIfNotNull(#Nullable V pValue, Function<Predicate, WhereClauseBuilder> pFunction, LazyBooleanExpression pBooleanExpression)
{
if (pValue != null)
{
return new WhereClauseBuilder(pFunction.apply(pBooleanExpression.get()));
}
return this;
}
}
#FunctionalInterface
public interface LazyBooleanExpression
{
BooleanExpression get();
}
And then the usage would be much cleaner:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
return empployeeDAO.findAll
(
new WhereClauseBuilder()
.optionalAnd(firstName, () -> employee.firstName.eq(firstName))
.optionalAnd(lastName, () -> employee.lastName.eq(lastName))
);
}
It is possible also to use jdk's Optional class
This is Java 101 actually: check for null and initialize the query instead of concatenating predicates. So a helper method like this could do the trick:
private BooleanExpression createOrAnd(BooleanExpression left, BooleanExpression right) {
return left == null ? right : left.and(right);
}
Then you can simply do:
BooleanExpression query = null;
if (firstName != null) {
query = createOrAnd(query, employee.firstName.eq(firstName));
}
if (lastName != null) {
query = createOrAnd(query, employee.lastName.eq(lastName));
}
…
Note, that I use createOrAnd(…) even in the first clause simply for consistency and to not have to adapt that code in case you decide to add a new clause even before the one for firstName.
if you check the QueryDSL implementation of null:
public BooleanExpression and(#Nullable Predicate right) {
right = (Predicate) ExpressionUtils.extract(right);
if (right != null) {
return BooleanOperation.create(Ops.AND, mixin, right);
} else {
return this;
}
}
which is supposedly what you want.
I faced same problem and here comes another version of Timo Westkämper
's accepted answer using the Optional.
default Optional<Correlation> findOne(
#Nonnull final String value, #Nullable final String environment,
#Nullable final String application, #Nullable final String service) {
final QSome Some = QSome.some;
final BooleanBuilder builder = new BooleanBuilder();
ofNullable(service).map(some.service::eq).map(builder::and);
ofNullable(application).map(some.application::eq).map(builder::and);
ofNullable(environment).map(some.environment::eq).map(builder::and);
builder.and(some.value.eq(value));
return findOne(builder);
}
For any one wants to build predicate based on dynamic request parameters map instead of specific ones can use the following simple format,
public List<User> searchUser(Map<String, Optional<String>> requestParams ){
QUser qUser = Quser.qUser;
BooleanBuilder builder = new BooleanBuilder();
requestParams.forEach( (String key, String value) -> {
if(!value.isEmpty()) {
StringPath column = Expressions.stringPath(qUser, key);
builder.and(column.eq(value));
}
});
}
And here is my controller
#RequestMapping(value = "", method = RequestMethod.GET)
public ResponseEntity<List<User>> searchUser(
#RequestParam() Map<String, Optional<String>> requestParams) {
List<User> userList = userService.searchUser(requestParams);
if(userList!=null)
return new ResponseEntity<>(userList, HttpStatus.OK);
else
return new ResponseEntity<>(userList, HttpStatus.INTERNAL_SERVER_ERROR);
}
Base on what you need i would do this
public List<EmployeeEntity> getEmployees(Optional<String> firstName, Optional<String> lastName)
{
BooleanExpression queryPredicate = QEmployeeEntity.employeeEntity.firstName.containsIgnoreCase(firstName.orElse("")).and(QEmployeeEntity.employeeEntity.lastName.containsIgnoreCase(lastName.orElse("")));
return empployeeDAO.findAll(queryPredicate);
}
First of all you should return a List of EmployeeEntity. Second, its better to use optional than checking if its null, and you may pass Java 8's Optional values obtained from optional RequestParam ones like this:
#RequestMapping(value = "/query", method = RequestMethod.GET)
public ModelAndView queryEmployee(#RequestParam(value = "firstName", required = false) Optional<String> firstName, #RequestParam(value = "lastName", required = false) Optional<String> lastName)
{
List<EmployeeEntity> result = getEmployees(firstName, lastName);
....
}
And a very important thing is to use the containsIgnoreCase function in the predicate: its better than a typical like cause its case insensitive.
In my opinion you should use some approach like this:
#Controller
class UserController {
#Autowired UserRepository repository;
#RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, #QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, #RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
look it at here.
This is a very simple way to deal with optional parameters, I use it in my project :
public List<ResultEntity> findByOptionalsParams(String param1, Integer param2) {
QResultEntity qResultEntity = QResultEntity.resultEntity;
final JPQLQuery<ResultEntity> query = from(qResultEntity);
if (!StringUtils.isEmpty(param1)) {
query.where(qResultEntity.field1.like(Expressions.asString("%").concat(param1).concat("%")));
}
if (param2 != null) {
query.where(qResultEntity.field2.eq(param2));
}
return query.fetch();
}
There is another way using Optional without BooleanBuilder although the resulting query might be a bit verbose:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression where = ofNullable(firstName).map(employee.firstName::eq).orElse(Expressions.TRUE)
.and(ofNullable(lastName).map(employee.lastName::eq).orElse(Expressions.TRUE));
return empployeeDAO.findAll(where);
}
Taking that idea and adding a helper function improves readability though:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression where = optionalExpression(firstName, employee.firstName::eq)
.and(optionalExpression(lastName, employee.lastName::eq));
return empployeeDAO.findAll(where);
}
public static <T> BooleanExpression optionalExpression(T arg, Function<T, BooleanExpression> expressionFunction) {
if (arg == null) {
return Expressions.TRUE;
}
return expressionFunction.apply(arg);
}

Categories