如何使用懒加载和分页查询Primefaces dataTable的数据

25

在我的JSF数据表中,我已经实现了lazy loading,当我翻页时,执行下一组记录需要大约4或5秒钟的时间,实际上应该只需要不到一秒钟的时间来执行结果。

这是由于我实现的方式引起的,不确定如何解决这个问题。

DataModel类扩展了LazyDataModel。

@Override
public List<Request> load(int startingAt, int maxPerPage, String sortField,
                          SortOrder sortOrder, Map<String, String> filters)
{
    requestList = requestService.getRequest(startingAt, maxPerPage,
                                            sortField, sortOrder, filters);
    this.setRowCount(requestList.size());
    if (requestList.size() > maxPerPage)
    {
        System.out.println("executing");
        return requestList.subList(startingAt, startingAt + maxPerPage);
    }
    else
    {
        System.out.println("executing else ");
        return requestList;
    }

    return requestList;
}

和在 dao 类中

@Override
public List<Request> getRequest(int startingAt, int maxPerPage,
                                String sortField, SortOrder sortOrder, Map<String, String> filters)
{
    Criteria criteria = sessionFactory.getCurrentSession().createCriteria(
                            Request.class);
    criteria.addOrder(Order.desc("requestNo"));
    for (Map.Entry<String, String> entry : filters.entrySet())
    {
        if (entry.getValue() != null)
        {
            criteria.add(Restrictions.ilike("requestNo",
                                            "%" + entry.getValue() + "%"));
        }
    }
    //criteria.setMaxResults(maxPerPage);
    //criteria.setFirstResult(startingAt);
    return criteria.list();
}

if (requestList.size() > maxPerPage)
{
    System.out.println("executing");
    return requestList.subList(startingAt, startingAt + maxPerPage);
}
else
{
    System.out.println("executing else ");
    return requestList;
}

如果执行,那么它会完美地执行而没有延迟,但问题是 this.setRowCount(requestList.size()); 总是5,这是每页默认记录数。

更新2

@Override
    public List<Request> load(int startingAt, int maxPerPage, String sortField,
            SortOrder sortOrder, Map<String, String> filters) {
        requestList = requestService.getRequest(startingAt, maxPerPage,
                sortField, sortOrder, filters);
        this.setRowCount(requestService.getRequestCount());
        if (requestService.getRequestCount() > maxPerPage) {
            try {

                return requestList.subList(startingAt, startingAt + maxPerPage);
            } catch (IndexOutOfBoundsException e) {
                //e.printStackTrace();
                return requestList.subList(startingAt, startingAt
                        + (requestService.getRequestCount() % maxPerPage));
            }
        } else {
            return requestList;
        }       
    }

使用以下查询获取结果集计数的不同方法:

@Override
    public int count() {
        int count = ((Long) sessionFactory.getCurrentSession()
                .createQuery("select count(*) from Request").uniqueResult())
                .intValue();
        System.out.println(" count size " + count);
        return count;
    }

和我的道

@Override
        public List<Request> getRequest(int startingAt, int maxPerPage,
                String sortField, SortOrder sortOrder, Map<String, String> filters) {
            Criteria criteria = sessionFactory.getCurrentSession().createCriteria(
                    Request.class);
            criteria.addOrder(Order.desc("requestNo"));
            for (Map.Entry<String, String> entry : filters.entrySet()) {
                if (entry.getValue() != null) {
                    criteria.add(Restrictions.ilike("requestNo",
                            "%" + entry.getValue() + "%"));         }
            }
             criteria.setMaxResults(maxPerPage);
             criteria.setFirstResult(startingAt);       
                return criteria.list(); 

        }
4个回答

34

当返回结果列表很大时,Java端的计数子列表操作可能会对内存使用和性能产生危险。

相反,我通常采用以下方法:使用2个查询,一个用于计算过滤后的结果集(我让数据库进行计数),另一个用于检索分页结果集(我让数据库提取子列表)。 即使是包含数百万行的表,我也从未遇到过显着的延迟。

以下是一个具体的示例,其中包括排序和过滤。所有代码都使用JPA标准(没有Hibernate或Spring的自定义功能)。在这种情况下,特别适用于CriteriaQuery方法。

我的Bean类

@ManagedBean
@ViewScoped
public class MyBean {
    @EJB
    private MyObjFacade myObjFacade;
    private LazyDataModel<MyObjType> model;        // getter and setter

    @PostConstruct
    public void init() {
        model = new LazyDataModel<MyObjType> () {

            @Override
            public List<MyObjType> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, String> filters) {
                model.setRowCount(myObjFacade.count(filters));
                return myObjFacade.getResultList(first, pageSize, sortField, sortOrder, filters);
            }
        };
        model.setRowCount(myObjFacade.count(new HashMap<String, String> ()));
    }
}

MyObjFacade类

@Stateless
public class MyObjFacade {
    @PersistenceContext
    private EntityManager em;
    @EJB
    private MyObjFacade myObjFacade;

    private Predicate getFilterCondition(CriteriaBuilder cb, Root<MyObjType> myObj, Map<String, String> filters) {
        Predicate filterCondition = cb.conjunction();
        String wildCard = "%";
        for (Map.Entry<String, String> filter : filters.entrySet()) {
            String value = wildCard + filter.getValue() + wildCard;
            if (!filter.getValue().equals("")) {
                javax.persistence.criteria.Path<String> path = myObj.get(filter.getKey());
                filterCondition = cb.and(filterCondition, cb.like(path, value));
            }
        }
        return filterCondition;
    }

    public int count(Map<String, String> filters) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Long> cq = cb.createQuery(Long.class);
        Root<MyObjType> myObj = cq.from(MyObjType.class);
        cq.where(myObjFacade.getFilterCondition(cb, myObj, filters));
        cq.select(cb.count(myObj));
        return em.createQuery(cq).getSingleResult().intValue();
    }

    public List<MyObjType> getResultList(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, String> filters) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<MyObjType> cq = cb.createQuery(MyObjType.class);
        Root<MyObjType> myObj = cq.from(MyObjType.class);
        cq.where(myObjFacade.getFilterCondition(cb, myObj, filters));
        if (sortField != null) {
            if (sortOrder == SortOrder.ASCENDING) {
                cq.orderBy(cb.asc(myObj.get(sortField)));
            } else if (sortOrder == SortOrder.DESCENDING) {
                cq.orderBy(cb.desc(myObj.get(sortField)));
            }
        }
        return em.createQuery(cq).setFirstResult(first).setMaxResults(pageSize).getResultList();
    }
}

1
正确。该解决方案符合JPA规范的Criteria API。Hibernate是该规范的一种实现,但也存在其他实现。 - perissf
在javax.persistence.criteria.Path<String>中,"order"是什么意思?path = order.get(filter.getKey()); - user1073494
"private List<Site> data = new ArrayList<Site>();" 是用来做什么的? - user1073494
@LeonardoKenji 感谢您指出,代码示例中有两个错误。 - perissf
@perissf 顺便说一下,我在 http://leonotepad.blogspot.com.br/2014/01/primefaces-datatable-lazy-loading-with.html 上发布了一个更完整的示例。对于 Path<String>,你可以使用 like,但对于其他 Path<?>,我的建议是使用 equal。此外,对于 Oracle,如果 Path<String> 是 CLOB,则会出现 Oracle 错误 ORA-00932。 - user1073494
显示剩余7条评论

2

我不确定这个在这种情况下是否相关,但是根据@perissf的观察,我会关注以下几点:

if (entry.getValue() != null)
{
    criteria.add(Restrictions.ilike("requestNo",
                                    "%" + entry.getValue() + "%"));
}

对于这个问题,可以将其转化为类似于以下查询:

WHERE UPPER(request_no) LIKE '%VALUE%'

如果在此情况下使用request_no索引将无法使用, 将会进行完整表扫描, 这对于具有大量行的表而言速度非常慢,有两个原因:

  • UPPER(request_no)需要一个函数索引。
  • like '%anything'将不得不查找request_no的每个值,而不管是否存在函数索引。

谢谢您指出这一点。那么避免全表扫描的最佳方法是什么? - Jacob
1
取决于你想做什么。在请求号码中查找子字符串是否有意义?你能接受 like 'value%' 吗?requestNo 是一个数字还是实际上是一个字符串? - beny23
针对那些问题,重新提出另一个问题可能是值得的。 - beny23

1

自从 PrimeFaces 11 版本以后, JpaLazyDataModel (源代码) 已经与 PrimeFaces 绑定在一起了。你可以这样使用它:

new JpaLazyDataModel<>(MyEntity.class, () -> entityManager);

如果您正在使用选择功能,可以在构造函数中传递rowKey字段名称或转换器(请参阅链接的文档)。


1

有一个库可以自动实现所有这些功能: https://docs.flowlogix.com/#section-jpa-lazymodel / https://github.com/flowlogix/flowlogix

@Named
@ViewScoped
public class UserViewer implements Serializable {
    private @Getter final JPALazyDataModel<UserEntity, Long> lazyModel =
            JPALazyDataModel.create(builder -> builder
                    .entityClass(UserEntity.class)
                    // the line below is optional, default is case-sensitive (true)
                    .caseSensitiveQuery(false)
                    .build());
}

声明:我是维护者


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接