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

Tin*_*iny 3 java criteria-api primefaces jsf-2 jpa-2.0

我正在使用,

  • 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<p:dataTable>...</p:dataTable>.

所述DataTable列标题中包含一个可点击的排序区,<div>用于与用于分选,当该区域被点击时,一个字符串,可以是排序方向的每一列ASCENDINGDESCENDING表示排序顺序呈现和用于其中用户输入滤波(搜索)的文本框每列的搜索项.


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

并且将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();        
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Bal*_*usC 5

如果删除SingularAttribute值的使用并确保调用者在排序/过滤器字段中使用所需的列名称调用方法,那么只需重复使用迭代排序/过滤器字段作为列名称,就可以将其简化得更多需要对字段进行if/else检查才能指定正确的列名(实际上它与排序/过滤器字段名称完全相同).

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

这是你如何重构它然后:

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

    // ...
}
Run Code Online (Sandbox Code Playgroud)

请注意,我还将方法修改为不接受null排序和过滤元,以便您可以安全地删除所有这些空检查.那些空检查是不必要的,因为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]);
}
Run Code Online (Sandbox Code Playgroud)