Criteria Resolver

A library to ease the use of criteria queries with multiple optional fields and projections

License

License

GroupId

GroupId

com.github.andreldsr
ArtifactId

ArtifactId

criteriaresolver
Last Version

Last Version

1.0.0
Release Date

Release Date

Type

Type

jar
Description

Description

Criteria Resolver
A library to ease the use of criteria queries with multiple optional fields and projections
Project URL

Project URL

http://github.com/andreldsr/criteriaresolver
Source Code Management

Source Code Management

http://github.com/andreldsr/criteriaresolver/tree/main

Download criteriaresolver

How to add to project

<!-- https://jarcasting.com/artifacts/com.github.andreldsr/criteriaresolver/ -->
<dependency>
    <groupId>com.github.andreldsr</groupId>
    <artifactId>criteriaresolver</artifactId>
    <version>1.0.0</version>
</dependency>
// https://jarcasting.com/artifacts/com.github.andreldsr/criteriaresolver/
implementation 'com.github.andreldsr:criteriaresolver:1.0.0'
// https://jarcasting.com/artifacts/com.github.andreldsr/criteriaresolver/
implementation ("com.github.andreldsr:criteriaresolver:1.0.0")
'com.github.andreldsr:criteriaresolver:jar:1.0.0'
<dependency org="com.github.andreldsr" name="criteriaresolver" rev="1.0.0">
  <artifact name="criteriaresolver" type="jar" />
</dependency>
@Grapes(
@Grab(group='com.github.andreldsr', module='criteriaresolver', version='1.0.0')
)
libraryDependencies += "com.github.andreldsr" % "criteriaresolver" % "1.0.0"
[com.github.andreldsr/criteriaresolver "1.0.0"]

Dependencies

compile (2)

Group / Artifact Type Version
jakarta.persistence : jakarta.persistence-api jar 2.2.3
io.swagger : swagger-annotations jar 1.6.2

Project Modules

There are no modules declared in this project.

Criteria Resolver

A library to ease the usage of Criteria Queries with multiple and optional fields and projections for your response

Sumário

Componentes

public abstract class SearchObject

  • Classe abstrata que deve ser estendida pelo objeto que representa a consulta a ser realizada pelo CriteriaResolver
public abstract class SearchObject {
    @ApiModelProperty(hidden = true)
    private Map<String, JoinType> joins = new HashMap<>();
    public Map<String, JoinType> getJoins() {
        return joins;
    }

    public void createJoins() {

    }

    public SearchObject(){
        this.createJoins();
    }
}
  • O Método createJoins() pode ser sobrescrito para definir os joins adicionando ao mapa joins, sendo a chave o nome da propriedade e o valor o tipo de Join
    • Exemplo de uso
    @Override
    public void createJoins() {
        this.getJoins().put("entidade", JoinType.LEFT);
    }

public @interface CriteriaField

  • Anotação criada para definir o campo de uma classe que estende SearchObject como um campo de consulta que vai gerar uma restrição na query.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CriteriaField {
    String fieldName() default "";
    ComparationType comparationType() default ComparationType.EQUALS;

    enum ComparationType {
        EQUALS, LIKE, GREATER_THAN, LESS_THAN, GREATER_EQUALS, LESS_EQUALS, IN, NOT_IN, DIFFERENT, STARTS_WITH, ENDS_WITH
    }
}
  • Por padrão a consulta vai ser gerada utilizando o nome da propriedade anotada como o nome do campo pesquisado, mas pode ser alterado pela propriedade fieldName, servindo também para acessar propriedades de objetos aninhados.
  • A propriedade comparationType define o tipo de comparação que deve ser feita na consulta, seu valor deve ser uma opção do Enum interno ComparationType e seu valor padrão é EQUALS
    • Exemplo de uso
@Getter
@Setter
public class ProductSearchObject extends SearchObject {
    @CriteriaField(comparationType = CriteriaField.ComparationType.LIKE)
    private String name;
    @CriteriaField(comparationType = CriteriaField.ComparationType.LIKE)
    private String description;
    @CriteriaField(fieldName = "price", comparationType = CriteriaField.ComparationType.GREATER_EQUALS)
    private Double minPrice;
    @CriteriaField(fieldName = "price", comparationType = CriteriaField.ComparationType.LESS_EQUALS)
    private Double maxPrice;
    @CriteriaField(fieldName = "price")
    private Double exactPrice;
    @CriteriaField(fieldName = "category.name", comparationType = CriteriaField.ComparationType.LIKE)
    private String category;
}
  • Apenas os campos preenchidos serão considerados na hora de gerar a consulta. Qualquer campo vazio é apenas ignorado.

public @interface ProjectionField

  • Anotação criada para potencializar o uso de DTOs no retorno das consultas geradas utilizando CriteriaResolver. Uma consulta genérica pode ter seus campos de retorno definidos ao se passar como parâmetro uma Classe desejada.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ProjectionField {
    String projectionPath();
}
  • O campo projectionPath define o nome da propriedade que se deseja retornada, seu valor padrão é o nome da propriedade anotada e pode ser utilizada para acessar propriedades de objetos aninhados. A anotação é opcional caso o nome da propriedade da classe de retorno seja igual ao do campo desejado.
    • Exemplo
public class ProductDTO {
    @ProjectionField(projectionPath = "name")
    private String productName;
    private Double price;
    @ProjectionField(projectionPath = "category.name")
    private String categoryName;
}
  • A classe passada como retorno deve ter um construtor com todos os argumentos.

public abstract class CriteriaResolverBaseRepository

  • Repositório base que deve ser estendido para gerar as consultas com CriteriaResolver
public abstract class CriteriaResolverBaseRepository<T> {
    EntityManager em;

    private Class<T> c;
    private CriteriaBuilder criteriaBuilder;
    private CriteriaQuery<T> criteria;
    private Root<T> root;
    private Map<String, Join> joinMap;

    @SuppressWarnings("unchecked")
    public CriteriaResolverBaseRepository(EntityManager entityManager){
        ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
        this.c = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
        this.em = entityManager;
        joinMap = new HashMap<>();
    }

    public List<T> getResultList(SearchObject searchObject){
        return this.getQuery(searchObject).getResultList();
    }

    public T getSingleResult(SearchObject searchObject) {
        return this.getQuery(searchObject).getSingleResult();
    }

    @SuppressWarnings("unchecked")
    public <D> TypedQuery<D> getGenericQuery(SearchObject searchObject, Class<D> clazz){
        CriteriaQuery<D> genericCriteria;
        criteriaBuilder = em.getCriteriaBuilder();
        genericCriteria = criteriaBuilder.createQuery(clazz);
        root = genericCriteria.from(c);
        createJoins(searchObject);
        setProjections(genericCriteria, clazz);
        List<Predicate> predicates = getPredicates(root, searchObject);
        genericCriteria.where(predicates.toArray(new Predicate[0]));
        return em.createQuery(genericCriteria);
    }

    public TypedQuery<T> getQuery(SearchObject searchObject){
        criteriaBuilder = em.getCriteriaBuilder();
        criteria = criteriaBuilder.createQuery(c);
        root = criteria.from(c);
        createJoins(searchObject);
        List<Predicate> predicates = getPredicates(root, searchObject);
        criteria.where(predicates.toArray(new Predicate[0]));
        return em.createQuery(criteria);
    }

    private void setProjections(CriteriaQuery genericCriteria, Class clazz) {
        List<String> projections = new ArrayList();
        for(Field field: clazz.getDeclaredFields()) {
            projections.add(getProjection(field));
        }
        List<Selection> selectionList = new ArrayList<>();
        Selection[] selectionArray;
        if(projections.size() == 0)
            return;
        for (String projection : projections) {
            selectionList.add(getPath(projection));
        }
        selectionArray = new Selection[selectionList.size()];
        genericCriteria.multiselect(selectionList.toArray(selectionArray));
    }

    private String getProjection(Field field) {
        field.setAccessible(true);
        String projectionPath = field.getName();
        ProjectionField[] annotationsByType = field.getAnnotationsByType(ProjectionField.class);
        if(annotationsByType != null && annotationsByType.length == 1) {
            projectionPath = annotationsByType[0].projectionPath();
        }
        return projectionPath;
    }

    private void createJoins(SearchObject searchObject) {
        Map<String, JoinType> joins = searchObject.getJoins();
        Set<String> keySet = joins.keySet();
        for (String key : keySet) {
            joinMap.put(key, root.join(key, joins.get(key)));
        }
    }

    private List<Predicate> getPredicates(Root<T> root, SearchObject searchObject) {
        List<Predicate> predicates = new ArrayList<>();
        for(Field field: searchObject.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName;
            CriteriaField.ComparationType comparationType;
            CriteriaField[] annotationsByType = field.getAnnotationsByType(CriteriaField.class);
            if(annotationsByType.length == 1) {
                try {
                    Object value = field.get(searchObject);
                    if(value == null)
                        continue;
                    CriteriaField criteriaField = annotationsByType[0];
                    if(criteriaField.fieldName().equals("")) {
                        fieldName = field.getName();
                    }else {
                        fieldName = criteriaField.fieldName();
                    }
                    comparationType = criteriaField.comparationType();
                    predicates.add(getPredicate(fieldName, comparationType, value));
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return predicates;
    }

    private Path getPath(String attributeName) {
        Path path = root;
        for (String part : attributeName.split("\\.")) {
            path = path.get(part);
        }
        return path;
    }

    @SuppressWarnings("unchecked")
    private Predicate getPredicate(String fieldName, CriteriaField.ComparationType comparationType, Object value) {
        Predicate predicate = null;
        switch(comparationType) {
            case LIKE:
                predicate = criteriaBuilder.like(getPath(fieldName), "%" +value + "%");
                break;
            case STARTS_WITH:
                predicate = criteriaBuilder.like(getPath(fieldName), value + "%");
                break;
            case ENDS_WITH:
                predicate = criteriaBuilder.like(getPath(fieldName), "%" +value);
                break;
            case GREATER_THAN:
                predicate = criteriaBuilder.greaterThan(getPath(fieldName), (Comparable) value);
                break;
            case GREATER_EQUALS:
                predicate = criteriaBuilder.greaterThanOrEqualTo(getPath(fieldName), (Comparable) value);
                break;
            case LESS_THAN:
                predicate = criteriaBuilder.lessThan(getPath(fieldName), (Comparable) value);
                break;
            case LESS_EQUALS:
                predicate = criteriaBuilder.lessThanOrEqualTo(getPath(fieldName), (Comparable) value);
                break;
            case IN:
                predicate = criteriaBuilder.in(getPath(fieldName)).value(value);
                break;
            case NOT_IN:
                predicate = criteriaBuilder.in(getPath(fieldName)).value(value).not();
                break;
            case DIFFERENT:
                predicate = criteriaBuilder.notEqual(getPath(fieldName), value);
                break;
            default:
                predicate = criteriaBuilder.equal(getPath(fieldName), value);
                break;
        }
        return predicate;
    }
}
  • Os métodos básicos para o funcionamento das consultas básicas já estão implementados, sendo necessário apenas criar um repositório que defina o ponto inicial das consultas.
    • Exemplo
@Repository
public class ProductCriteriaRepository extends CriteriaBaseRepository<Product> {
    public ProductCriteriaRepository(EntityManager entityManager) {
        super(entityManager);
    }
}
  • O repositório já está pronto para o uso com os métodos getQuery(SearchObject searchObject), getSingleResult(SearchObject searchObject), getResultList(SearchObject searchObject) e getGenericQuery(SearchObject searchObject, Class clazz)
    • Exemplo
public List<ProductDTO> getProductByCriteria(ProductSearchObject productSearchObject){
    return productCriteriaRepository.getGenericQuery(productSearchObject, ProductDTO.class).setFirstResult(10).setMaxResults(10).getResultList();
}
  • Esse código sendo o suficiente para realizar uma consulta paginada apenas pelos campos preenchidos do objeto de consulta e com as projeções no formato do objeto de retorno.

Versions

Version
1.0.0