【问题标题】:Facing Critical Performance issue in Primefaces 4 & 5在 Primefaces 4 和 5 中面临关键性能问题
【发布时间】:2014-07-11 09:18:26
【问题描述】:

我正在处理一个处理大量数据集的项目。我正在使用 Primefaces 4 和 5、spring 和 hibernate。我必须显示一个非常庞大的数据集,例如最少 3000 行和 100 列具有各种功能,例如排序、过滤、行扩展等。我的问题是,我的应用程序需要 8 到 10 分钟才能显示整个页面以及其他功能(排序、过滤)也需要很多时间。我的客户一点也不高兴。但是我可以为此使用分页,但我的客户又不想分页。所以我决定使用 livescroll,但不幸的是我未能实现 livescroll with lazyload 或 withoutlazyload,因为 PF 中存在有关 livescroll 的错误。我也发布了这个问题here earlier,但没有找到解决方案。

这个性能问题非常关键,对我来说很重要。要显示 3000 行和 100 列,正在加载的页面大小约为 10MB。 我使用 Phase-listener 计算了 JSF 的各种 生命周期 所消耗的时间,我发现它的浏览器正在花时间解析 jsf 呈现的响应.为了完成所有阶段,我的申请只用了 25 秒。 至少我想提高我的项目的性能。请分享任何可以帮助解决此问题的想法、建议和任何内容

注意:getter和setter中没有数据库操作,也没有复杂的业务逻辑。

更新: 这是我没有延迟加载的数据表:

<p:dataTable 
                style="width:100%"
                id="cdTable"
                selection="#{controller.selectedArray}"
                resizableColumns="true" 
                draggableColumns="true" 
                var="cd"
                value="#{controller.cdDataModel}"
                editable="true" 
                editMode="cell"
                selectionMode="multiple"
                rowSelectMode="add"
                scrollable="true"
                scrollHeight="650"
                rowKey="#{cd.id}"
                rowIndexVar="rowIndex"
                styleClass="screenScrollStyle"
                liveScroll="true"
                scrollRows="50"
                filterEvent="enter"
                widgetVar="dt4"
                >

这里一切正常,除了过滤。一旦我过滤,就会显示第一页,但无法在数据表上排序或滚动。请注意,我在 Primefaces5 中测试过。

第二次接近

具有相同数据表的延迟加载 1) 当我添加 rows="100" 时,会发生动态滚动,但行编辑、行扩展存在问题,但过滤和排序有效。 2) 当我删除 rows 时,livescroll 可用于行编辑、行扩展等,但过滤和排序不起作用。

我的 LazyLoadModel 如下

public class MyDataModel extends LazyDataModel<YData> 
         {

    @Override
    public List<YData> load(int first, int pageSize,
            List<SortMeta> multiSortMeta, Map<String, Object> filters) {
        System.out.println("multisort wala load");
        return super.load(first, pageSize, multiSortMeta, filters);
    }



    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private List<YData> datasource;

    public YieldRecBondDataModel() {
    }

    public YieldRecBondDataModel(List<YData> datasource) {
         this.datasource = datasource;
    }




    @Override
    public YData getRowData(String rowKey) {
        // In a real app, a more efficient way like a query by rowKey should be
        // implemented to deal with huge data

    //  List<YData> yList = (List<YData>) getWrappedData();

        for (YData y : datasource) 
        {
            System.out.println("datasource :"+datasource.size());
            if(y.getId()!=null)
            {
            if (y.getId()==(new Long(rowKey)))
            {
                return y;
            }
            }
        }

        return null;
    }

    @Override
    public Object getRowKey(YData y) {
        return y.getId();
    }






      @Override
        public void setRowIndex(int rowIndex) {
            /*
             * The following is in ancestor (LazyDataModel):
             * this.rowIndex = rowIndex == -1 ? rowIndex : (rowIndex % pageSize);
             */
            if (rowIndex == -1 || getPageSize() == 0) {
                super.setRowIndex(-1);
            }
            else
                super.setRowIndex(rowIndex % getPageSize());
        }



      @Override
        public List<YData> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String,Object> filters) {
            List<YData> data = new ArrayList<YData>();
            System.out.println("sort order : "+sortOrder);
            //filter
            for(YData yInfo : datasource) {
                boolean match = true;

                for(Iterator<String> it = filters.keySet().iterator(); it.hasNext();) {
                    try {
                        String filterProperty = it.next();
                        String filterValue = String.valueOf(filters.get(filterProperty));
                        Field yField = yInfo.getClass().getDeclaredField(filterProperty);
                        yField.setAccessible(true);
                        String fieldValue = String.valueOf(yField.get(yInfo));
                        if(filterValue == null || fieldValue.startsWith(filterValue)) {
                            match = true;
                        }
                        else {
                            match = false;
                            break;
                        }
                    } catch(Exception e) {
                        e.printStackTrace();
                        match = false;
                    }
                }

                if(match) {
                    data.add(yInfo);
                }
            }

            //sort
            if(sortField != null) {
                Collections.sort(data, new LazySorter(sortField, sortOrder));
            }

            int dataSize = data.size();
            this.setRowCount(dataSize);

            //paginate
            if(dataSize > pageSize) {
                try {

                    List<YData> subList = data.subList(first, first + pageSize);
                    return subList;
                }
                catch(IndexOutOfBoundsException e) {
                    return data.subList(first, first + (dataSize % pageSize));
                }
            }
            else
                return data;
        }

    @Override
    public int getRowCount() {
        // TODO Auto-generated method stub
        return super.getRowCount();
    }

    }

我对这些问题感到厌烦,并成为我的表演终结者。即使我尝试过 Primefaces 5

【问题讨论】:

  • 我看不到 100 列适合页面的位置,但无论如何分页是有效的方式(毕竟有一个限制)想象谷歌搜索结果会出现在页面中3000个结果!分页和延迟加载是解决方案,当然也推荐数据库索引。实时滚动问题必须在最新的 5 版本中修复。
  • @HatemAlimam 感谢您的回复,请检查更新。问题是我的客户不想要分页。这就是为什么我必须坚持使用 livescroll,但 livescroll 本身包含错误。
  • 延迟加载还有其他问题。您无法在客户端页面中进行搜索。如果您使用服务器端过滤器,则滚动到正确的位置并不容易。 p:dataTable 可以替换为性能更好的 h:dataTable。大表在浏览器客户端的性能方面存在问题。在网络技术中显示大表不是一个好主意!这是桌面应用程序的工作。过滤和分页要好得多。
  • @lada.dvorak...感谢您的建议。但是我的客户不接受分页。所以只剩下一个选项,即livescroll。我几乎搞定了,但过滤不能正常工作。我将尝试使用 h:datatable,除 h:datatable 之外的任何其他方法。
  • 也许你可以说服你的客户不使用网络界面进入这个......它似乎更适合桌面应用程序,如果你想坚持使用网络可能是小程序。嘿,如果他只想浏览数据,打开电子表格可能没问题。

标签: jsf-2 primefaces primefaces-extensions


【解决方案1】:

您使用了 Primefaces 展示中使用的默认 load 实现。对于从数据库加载数据的情况,这不是正确的实现。

加载方法应该使用正确的查询并考虑:

1) 使用的过滤字段,例如:

String query = "select e from Entity e where lower(e.f1) like lower('" + filters.get(key) + "'%) and..., etc. for the other fields

2) 使用的排序列,例如:

query.append("order by ").append(sortField).append(" ").append(SortOrder.ASCENDING.name() ? "" : sortOrder.substring(0, 4)),..., etc. for the other columns.

3) 附加到它的带有 1) 的查询的总数。示例:

Long totalCount = (Long) entityManager.createQuery("select count(*) from Entity e where lower(e.f1) like lower('filterKey1%') and lower(e.f2) like lower('filterKey2%') ...").getSingleResult();

【讨论】:

    【解决方案2】:

    如果您的数据是从 db 加载的,我建议您使用更好的 LazyDataModel,例如:

     public class ElementiLazyDataModel extends LazyDataModel<T> implements Serializable {
    
            private Service<T> abstractFacade;
    
    
            public ElementiLazyDataModel(Service<T> abstractFacade) {
                this.abstractFacade = abstractFacade;
    
            }
    
            public Service<T> getAbstractFacade() {
                return abstractFacade;
            }
    
            public void setAbstractFacade(Service<T> abstractFacade) {
                this.abstractFacade = abstractFacade;
            }
    
            @Override
            public List<T> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
                PaginatedResult<T> pr = abstractFacade.findRange(new int[]{first, first + pageSize}, sortField, sortOrder, filters);
    
                setRowCount(new Long(pr.getTotalItems()).intValue());
    
                return pr.getItems();
            }
    
        }
    

    服务是某种后端通信(如 EJB),注入到使用此模型的 ManagedBean 中。

    分页的服务可能是这样的:

    @Override
        public PaginatedResult<T> findRange(int[] range, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
    
            final Query query = getEntityManager().createQuery("select x from " + entityClass.getSimpleName() + " x")
                    .setFirstResult(range[0]).setMaxResults(range[1] - range[0] + 1);
    
            // Add filter sort etc.
    
            final Query queryCount = getEntityManager().createQuery("select count(x) from " + entityClass.getSimpleName() + " x");
            // Add filter sort etc.
    
            Long rowCount = (Long) queryCount.getSingleResult();
    
            List<T> resultList = query.getResultList();
    
            return new PaginatedResult<T>(resultList, rowCount);
        }
    

    请注意,您必须执行分页查询(使用 jpa 这样的 orm 会为您执行查询,但如果您不使用 orm 必须执行分页查询,for oracle 查看 TOP-N 查询,例如:http://oracle-base.com/articles/misc/top-n-queries.php)

    请记住,您的返回 obj 还必须包含总记录作为快速计数:

    public class PaginatedResult<T> implements Serializable {
    
        private List<T> items;
        private long totalItems;
    
        public PaginatedResult() {
        }
    
        public PaginatedResult(List<T> items, long totalItems) {
            this.items = items;
            this.totalItems = totalItems;
        }
    
        public List<T> getItems() {
            return items;
        }
    
        public void setItems(List<T> items) {
            this.items = items;
        }
    
        public long getTotalItems() {
            return totalItems;
        }
    
        public void setTotalItems(long totalItems) {
            this.totalItems = totalItems;
        }
    }
    

    如果您的数据库表设置正确,所有这些都会很有用,请注意可能查询的执行计划并添加正确的索引。

    希望能给一些提示来提高你的表现

    最后,请记住,人眼不能一次看到超过 10-20 条记录,因此在一个页面中有数千条记录是非常没用的。

    【讨论】:

      猜你喜欢
      • 2013-11-09
      • 1970-01-01
      • 2019-11-21
      • 1970-01-01
      • 2019-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-20
      相关资源
      最近更新 更多