在基于LazyDataModel的排序/过滤字段创建JPA条件查询时,删除if-else梯形图

我正在使用,

  • JPA 2.0
  • Mojarra 2.1.9
  • JSF组件库,Primefaces 3.5。
  • MySQL 5.6.11

我在MySQL数据库中有一个名为state_table的表,以三列为例。

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

state_id是自动生成的主键, country_id是引用country表的主键的外键。


该表由其对应的名为StateTable实体类映射,该表持有的数据显示在Primefaces DataTable

...

DataTable列标题包含一个可单击的排序区域,每个列的

以及用于排序的排序方向,单击此区域时,将显示一个字符串,表示呈现排序顺序的DESCENDINGDESCENDING以及用于过滤的文本框(搜索)其中用户输入每列的搜索项。


因此,最终,我在JSF托管bean中得到的是一个类型为java.util.List的List,表示用户希望的DataTable列的排序顺序。

并且类型为java.util.Map表示搜索列名称作为键,并将相应列的搜索项作为值(搜索项由用户输入DataTable每列的列标题上的文本框)。


简而言之,我使用List进行排序,使用Map进行过滤/搜索。

在排序和过滤后获取行列表的其中一个DAO中的代码如下所示。

 @Override @SuppressWarnings("unchecked") public List getList(int first, int pageSize, List multiSortMeta, Mapfilters) { CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(StateTable.class); Metamodel metamodel=entityManager.getMetamodel(); EntityType entityType = metamodel.entity(StateTable.class); Rootroot=criteriaQuery.from(entityType); Join join = null; //Sorting List orders=new ArrayList(); 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 Listpredicates=new ArrayList(); if(filters!=null&&!filters.isEmpty()) { for(Entryentry: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 typedQuery = entityManager.createQuery(criteriaQuery).setFirstResult(first).setMaxResults(pageSize); return typedQuery.getResultList(); } 

这可以按预期工作,但是可以注意到, foreach循环中的if-else if梯形图可以包含许多条件检查,因为数据库表中的列数增加了。

每列都需要对排序和搜索进行条件检查。 是否有一种有效的方法可以摆脱这些条件检查,最终可以删除或至少最小化if-else if梯形图?

PS如果是国家/地区,我正在对countryName (在父表country可用)而不是countryId进行排序和搜索。 因此,在这种情况下,我正在使用Join

如果删除SingularAttribute值的使用并确保调用者在排序/filter字段中使用所需的列名称调用该方法,那么只需重复使用迭代排序/filter字段作为列名称即可简化它不需要对字段进行if / else检查以指定正确的列名(实际上它与排序/filter字段名称完全相同)。

基本上,你根本不需要那些equalsIgnoreCase()检查if-else梯形图。 对于区分大小写,如果调用者做错了,只需将其修复到那里,而不是对调用者的错误过于宽容。

这是你如何重构它然后:

 /** * @throws NullPointerException When multiSortMeta or filters argument is null. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public List getList(int first, int pageSize, List multiSortMeta, Map filters) { // ... Root root = criteriaQuery.from(entityType); Join join = root.join(StateTable_.countryId, JoinType.INNER); List orders = new ArrayList(); for (SortMeta sortMeta : multiSortMeta) { String[] sortField = sortMeta.getSortField().split("\\.", 2); Path path = sortField.length == 1 ? root.get(sortField[0]) : join.get(sortField[1]); orders.add(sortMeta.getSortOrder() == SortOrder.ASCENDING ? criteriaBuilder.asc(path) : criteriaBuilder.desc(path)); } Listpredicates = new ArrayList(); for (Entry 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() + "%")); } // ... } 

请注意,我还将方法修改为不接受null作为排序和过滤元,以便您可以安全地删除所有这些空检查。 那些空检查是不必要的,因为如果for循环为空, for循环将不会迭代。 另请注意,如果给出了数字输入,则过滤使用CriteriaBuilder#equal() ,否则使用like() 。 我不确定这是否涵盖了你的所有情况,你可能想要更多的微调。

您可以根据需要使用以下帮助方法重构Path的获取:

 @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]); }