Как сделать запрос данных для Primefaces dataTable, используя ленивую загрузку и разбиение на страницы

В моей базе данных JSF я реализовал ленивую загрузку, и когда я разбиваю записи на страницы, для выполнения следующего набора записей требуется около 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;
}

и в классе дао

@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 ответа

Решение

В случае очень больших результирующих списков подсчет на стороне Java и операции перечисления могут быть опасными для использования памяти и, следовательно, также для производительности.

Вместо этого я обычно использую следующий подход: использую 2 запроса, один для подсчета отфильтрованного результирующего набора (я позволяю БД делать подсчет), а другой для получения разбитого на страницы результирующего набора (я позволяю БД извлечь подсписок). Я никогда не испытывал значительных задержек, даже с таблицами, содержащими миллионы строк.

Следует конкретному примеру с сортировкой и фильтрацией. Весь код использует стандарт JPA (нет пользовательских функций Hibernate или Spring). CriteriaQuery подход особенно показан в таких ситуациях.

MyBean класс

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

Я не уверен, относится ли это к делу в данном случае, но, добавив к наблюдениям @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 независимо от того, присутствует функциональный индекс или нет.

Существует библиотека, которая реализует все это автоматически: 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());
}

Отказ от ответственности: я являюсь сопровождающим

Начиная с PrimeFaces 11,JpaLazyDataModel( источник ) входит в состав PrimeFaces. Вы можете использовать его как:

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

Если вы используете выбор, вы можете передатьrowKeyимя поля или конвертер в конструкторе (см. связанную документацию).

Другие вопросы по тегам