query namedquery example ejemplos create consultas java jsf-2 primefaces jpa-2.0 criteria-api

namedquery - jpql en java



Deshazte de la escalera if-else al crear una consulta de criterios JPA basada en los campos de clasificación/filtro de LazyDataModel (1)

Estoy usando,

  • JPA 2.0
  • Mojarra 2.1.9
  • Biblioteca de componentes JSF, Primefaces 3.5.
  • MySQL 5.6.11

Tengo una tabla en la base de datos MySQL llamada state_table con tres columnas como ejemplo.

  • state_id (BigInt)
  • state_name (Varchar)
  • country_id (BigInt)

state_id es una clave primaria generada automáticamente y country_id es una clave externa que hace referencia a una clave principal de la tabla de country .

Esta tabla se correlaciona con su clase de entidad correspondiente llamada StateTable y los datos que StateTable esta tabla se muestran en Primefaces DataTable , <p:dataTable>...</p:dataTable> .

El encabezado de columna DataTable contiene un área de ordenación seleccionable, <div> para cada columna con una dirección de clasificación para ordenar, cuando se hace clic en esta área, una cadena, ASCENDING o DESCENDING representa el orden de clasificación y un cuadro de texto para filtrar (buscar ) en el que un usuario ingresa un elemento de búsqueda para cada columna.

Así que, en última instancia, lo que obtengo en JSF managed bean es una Lista de tipo java.util.List<org.primefaces.model.SortMeta> representa órdenes de clasificación de las columnas de la DataTable que un usuario desea.

Y un Mapa de tipo java.util.Map<java.lang.String, java.lang.String> representa los nombres de las columnas de búsqueda como claves y los elementos de búsqueda de las columnas correspondientes como valores (un elemento de búsqueda es ingresado por un usuario en un cuadro de texto en el encabezado de columna de cada columna de DataTable ).

En resumen, utilizo List<SortMeta> para ordenar y Map<String, String> para filtrar / buscar.

Mi código en uno de los DAO para obtener una lista de filas después de ordenar y filtrar es el siguiente.

@Override @SuppressWarnings("unchecked") public List<StateTable> getList(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, String>filters) { CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<StateTable> criteriaQuery = criteriaBuilder.createQuery(StateTable.class); Metamodel metamodel=entityManager.getMetamodel(); EntityType<StateTable> entityType = metamodel.entity(StateTable.class); Root<StateTable>root=criteriaQuery.from(entityType); Join<StateTable, Country> join = null; //Sorting List<Order> orders=new ArrayList<Order>(); if(multiSortMeta!=null&&!multiSortMeta.isEmpty()) { for(SortMeta sortMeta:multiSortMeta) { if(sortMeta.getSortField().equalsIgnoreCase("stateId")) { orders.add(sortMeta.getSortOrder().equals(SortOrder.ASCENDING)?criteriaBuilder.asc(root.get(StateTable_.stateId)):criteriaBuilder.desc(root.get(StateTable_.stateId))); } else if(sortMeta.getSortField().equalsIgnoreCase("stateName")) { orders.add(sortMeta.getSortOrder().equals(SortOrder.ASCENDING)?criteriaBuilder.asc(root.get(StateTable_.stateName)):criteriaBuilder.desc(root.get(StateTable_.stateName))); } else if(sortMeta.getSortField().equalsIgnoreCase("country.countryName")) // Yes, Primefaces DataTable renders this ugly name in case of a nested property representing a foreign key relationship. { join = root.join(StateTable_.countryId, JoinType.INNER); orders.add(sortMeta.getSortOrder().equals(SortOrder.ASCENDING)?criteriaBuilder.asc(join.get(Country_.countryName)):criteriaBuilder.desc(join.get(Country_.countryName))); } } } //Filtering/searching List<Predicate>predicates=new ArrayList<Predicate>(); if(filters!=null&&!filters.isEmpty()) { for(Entry<String, String>entry:filters.entrySet()) { if(entry.getKey().equalsIgnoreCase("stateId")) { predicates.add(criteriaBuilder.equal(root.get(StateTable_.stateId), Long.parseLong(entry.getValue()))); } else if(entry.getKey().equalsIgnoreCase("stateName")) { predicates.add(criteriaBuilder.like(root.get(StateTable_.stateName), "%"+entry.getValue()+"%")); } else if(entry.getKey().equalsIgnoreCase("country.countryName"))// Yes, Primefaces DataTable renders this ugly name in case of a nested property representing a foreign key relationship. { if(join==null) { join = root.join(StateTable_.countryId, JoinType.INNER); } predicates.add(criteriaBuilder.like(join.get(Country_.countryName), "%"+entry.getValue()+"%")); } } } if(predicates!=null&&!predicates.isEmpty()) { criteriaQuery.where(predicates.toArray(new Predicate[0])); } if(orders!=null&&!orders.isEmpty()) { criteriaQuery.orderBy(orders); } else { criteriaQuery.orderBy(criteriaBuilder.desc(root.get(StateTable_.stateId))); } TypedQuery<StateTable> typedQuery = entityManager.createQuery(criteriaQuery).setFirstResult(first).setMaxResults(pageSize); return typedQuery.getResultList(); }

Esto funciona como se esperaba, pero como se puede notar, la escalera if-else if dentro del ciclo foreach puede contener muchas verificaciones condicionales a medida que aumenta el número de columnas en una tabla de base de datos.

Cada columna requiere una verificación condicional para ordenar y buscar. ¿Existe alguna manera eficiente de deshacerse de estas comprobaciones condicionales que, en última instancia, pueden eliminar o, al menos, minimizar este if-else if escalera?

PD En el caso de país, estoy ordenando y buscando en countryName (que está disponible en el country tabla padre) en lugar de countryId . Por lo tanto, estoy usando Join , en este caso.


Si omite el uso de los valores de SingularAttribute y se asegura de que la persona que llama el método exactamente con los nombres de columna deseados en los campos de clasificación / filtro, puede simplificarlo mucho más simplemente reutilizando el campo iterado de clasificación / filtro como nombre de columna sin la necesidad de una verificación if / else en el campo para especificar el nombre correcto de la columna (que después de todo es realmente idéntico al nombre del campo de clasificación / filtro).

Básicamente, no necesita esos cheques equalsIgnoreCase() en la escala if-else en absoluto. En cuanto a la distinción entre mayúsculas y minúsculas, si la persona que llama lo está haciendo mal, simplemente arréglelo en lugar de ser demasiado indulgente con los errores de la persona que llama.

Así es como podrías refactorizarlo:

/** * @throws NullPointerException When <code>multiSortMeta</code> or <code>filters</code> argument is null. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public List<?> getList(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, String> filters) { // ... Root<StateTable> root = criteriaQuery.from(entityType); Join<StateTable, Country> join = root.join(StateTable_.countryId, JoinType.INNER); List<Order> orders = new ArrayList<Order>(); for (SortMeta sortMeta : multiSortMeta) { String[] sortField = sortMeta.getSortField().split("//.", 2); Path<Object> path = sortField.length == 1 ? root.get(sortField[0]) : join.get(sortField[1]); orders.add(sortMeta.getSortOrder() == SortOrder.ASCENDING ? criteriaBuilder.asc(path) : criteriaBuilder.desc(path)); } List<Predicate>predicates = new ArrayList<Predicate>(); for (Entry<String, String> filter : filters.entrySet()) { String[] filterField = filter.getKey().split("//.", 2); Path path = filterField.length == 1 ? root.get(filterField[0]): join.get(filterField[1]); predicates.add(filter.getValue().matches("[0-9]+") ? criteriaBuilder.equal(path, Long.valueOf(filter.getValue())) : criteriaBuilder.like(path, "%" + filter.getValue() + "%")); } // ... }

Tenga en cuenta que también modifiqué el método para no aceptar null como ordenar y filtrar meta, de modo que pueda recortar de forma segura todas esas comprobaciones nulas. Esos controles vacíos son innecesarios ya que el bucle for no se repetirá de todos modos si está vacío. También tenga en cuenta que el filtro utiliza CriteriaBuilder#equal() si se proporciona una entrada numérica, de lo contrario, utiliza like() . No estoy seguro si eso cubre todos sus casos, es posible que desee ajustar eso más.

Si es necesario, puede refactorizar la obtención de Path aún más con el siguiente método de ayuda:

@SuppressWarnings("rawtypes") private static Path<?> getPath(String field, Root root, Join join) { String[] fields = field.split("//.", 2); return fields.length == 1 ? root.get(fields[0]): join.get(fields[1]); }