Lazy load - dynamic filter in Vaadin 10

Filtering the content of a grid lazily from a database is not an easy task if you have to concatenate many filters.

Consider an application in which is necessary to filter animals(pets), accordingly to its name, specie and owner.

The idea is to define a filter for animals, that contains the fields “text”/“name”, specie and owner and from that
filter create a set of predicates that will be assigned to the where clause of the Query. Criteria Api is used to generate
the query dynamically.

The filter is given to the ConfigurableFilterDataProvider data provider of the grid in the following way:

@Autowired
private AnimalService animalService;

/**
 * Data provider that fetches animasl from the DB dynamically accordingly to a animal filter.
 */
private ConfigurableFilterDataProvider<Animal, Void, AnimalFilter> dataProvider;
	
private void initDataProvider() {
    // Note: findAnyMatching(OR) or findAllMatching(AND)
    dataProvider = DataProvider.<Animal, AnimalFilter>fromFilteringCallbacks(
            query -> animalService.findAllMatching(query.getFilter(), query.getOffset(), query.getLimit(), query.getSortOrders()),
            query -> (int) animalService.countAllMatching(query.getFilter())).withConfigurableFilter();

    setDataProvider(dataProvider);
}

The predicates are generated from the filter:

 public List<Predicate> generatePredicates(AnimalFilter animalFilter, CriteriaBuilder cb, Root<Animal> root) {
    if ( animalFilter == null){
        return new ArrayList<>();
    }

    String text = animalFilter.getText();
    Specie specie = animalFilter.getSpecie();
    Owner owner = animalFilter.getOwner();

    List<Predicate> predicates = new ArrayList<>();

    // text
    if (text != null && !text.trim().isEmpty()) {
        String re = "%" + text.toLowerCase().trim() + "%";

        List<Predicate> termPredicates = new ArrayList<>();

        termPredicates.add(cb.like(cb.lower(root.get("name").as(String.class)), re));
        termPredicates.add(cb.like(cb.lower(root.get("description").as(String.class)), re));

        predicates.add(cb.or(termPredicates.toArray(new Predicate[0]
)));
    }

    if (specie != null) {
        predicates.add(cb.equal(root.get("specie"),specie));
    }

    if (owner != null){
        predicates.add(cb.equal(root.get("owner"),owner));
    }

    // other predicates(if needed)

    return predicates;
}

They can be concatenated in the following way:

List<Predicate> filters = generatePredicates(filter.get(), cb, root);

cq.where(cb.and(filters.toArray(new Predicate[filters.size()]
)));
cq.where(cb.or(filters.toArray(new Predicate[filters.size()]
)));

Note: when buildinng the query it is necessary to set the limit and offset for setting the lazy load
TypedQuery<Animal> animalsQuery = entityManager.createQuery(cq).setMaxResults(limit).setFirstResult(offset);

How to add more constraints?

Adding new constrains can be easily implemented by just adding a field to the filter and creating and extra predicate:

  1. Adding an extra field to the filter, e.g. age .
  2. Adding the predicate inside generatePredicates method.
if (age >= 0){
	predicates.add(cb.equal(root.get("age"),filter.getAge()));
}

Code:

The code can be found in this Github repository:
https://github.com/DiegoSanzVi/dynamic-filter

Result: