3

我在用着,

  • JPA 2.0
  • 莫哈拉 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,该表保存的数据显示在 PrimefacesDataTable<p:dataTable>...</p:dataTable>

DataTable标题包含一个可单击的排序区域,<div>对于每个列都有一个排序方向,当单击该区域时,将呈现一个字符串,ASCENDING或者表示排序顺序,以及一个用于过滤(搜索)的文本框,用户可以在其中输入DESCENDING每列的搜索项。


所以最终,我在 JSF 托管 bean 中得到的是一个 List 类型,表示用户希望java.util.List<org.primefaces.model.SortMeta>的列的排序顺序。DataTable

以及一个类型的 Map ,java.util.Map<java.lang.String, java.lang.String>将搜索列名称表示为键,将相应列的搜索项表示为值(搜索项由用户在 的每一列的列标题上的文本框中输入DataTable)。


简而言之,我List<SortMeta>用于排序和Map<String, String>过滤/搜索。

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

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

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

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

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

4

1 回答 1

5

If you drop the usage of SingularAttribute values and you make sure that the caller calls the method with exactly the desired column names in sort/filter fields, then you could simplify it a lot more by just reusing the iterated sort/filter field as column name without the need for an if/else check on the field in order to specify the right column name (which is after all actually identical to the sort/filter field name).

Essentially, you don't need those equalsIgnoreCase() checks in if-else ladder at all. As to case sensitivity, if the caller is doing it wrong, just fix it over there instead of being too forgiving on caller's mistakes.

Here's how you could refactor it then:

/**
 * @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() + "%"));
    }

    // ...
}

Note that I also modified the method to not accept null as sort and filter meta, so that you can safely trim out all those null checks. Those empty checks are unnecessary as the for loop won't iterate anyway if it's empty. Also note that the filtering uses CriteriaBuilder#equal() if a numeric input is given, otherwise it uses like(). I'm not sure if that covers all your cases, you may want to finetune that more.

You can if necessary refactor the obtaining of Path even more with the following helper method:

@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]);
}
于 2013-08-30T20:23:22.693 回答