Criteria Resolver

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

Лицензия

Лицензия

Группа

Группа

com.github.andreldsr
Идентификатор

Идентификатор

criteriaresolver
Последняя версия

Последняя версия

1.0.0
Дата

Дата

Тип

Тип

jar
Описание

Описание

Criteria Resolver
A library to ease the use of criteria queries with multiple optional fields and projections
Ссылка на сайт

Ссылка на сайт

http://github.com/andreldsr/criteriaresolver
Система контроля версий

Система контроля версий

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

Скачать criteriaresolver

Как подключить последнюю версию

<!-- 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"]

Зависимости

compile (2)

Идентификатор библиотеки Тип Версия
jakarta.persistence : jakarta.persistence-api jar 2.2.3
io.swagger : swagger-annotations jar 1.6.2

Модули Проекта

Данный проект не имеет модулей.

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.

Версии библиотеки

Версия
1.0.0