本文只针对ZK的分页数据表格这个主题,关于ZK框架的入门介绍见《ZK(The leading enterprise Ajax framework)入门指南》
https://blog.csdn.net/daquan198163/article/details/9304897?spm=1001.2014.3001.5501
概述
zk框架虽然提供了大量方便的开箱即用的组件,但是不得不说分页是个例外,无论是listbox还是grid,对于大数据量的场景都没有给出开箱即用的分页解决方案。
<listbox mold="paging">这种内存分页是无法满足真实应用的——并发量和数据量一高就把内存耗尽了。因此需要我们自行封装解决。
分页逻辑的模板类BaseSortPagingListModel
首先需要设计一个模板类把分页的通用逻辑、流程(构造criteria查询条件、获取总记录数、查询当前页数据、处理返回结果)封装起来,以便复用和扩展。
同时这个模板还需要能够与最终用来显示数据表格的zk组件集成。
因此最合理的方案就是基于AbstractListModel扩展出一个分页版本——BaseSortPagingListModel
package common;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.zul.AbstractListModel;
import org.zkoss.zul.FieldComparator;
import org.zkoss.zul.ListModelExt;
import org.zkoss.zul.event.ListDataEvent;
/**
* ZK分页ListModel基类,用于封装分页逻辑:构造criteria、获取总记录数、查询当前页数据、处理返回结果
* @param <TC> 查询条件model泛型
* @param <TM> 返回结果集中的model泛型
*/
@SuppressWarnings({ "unchecked", "serial", "rawtypes", "deprecation" })
abstract public class BaseSortPagingListModel<TC, TM> extends AbstractListModel<TM> implements ListModelExt {
protected static final Logger logger = LoggerFactory.getLogger(BaseSortPagingListModel.class);
private static final ThreadLocal<Map<String, Integer>> threadLocalTotalSize = new ThreadLocal<Map<String, Integer>>();
private static final ThreadLocal<Map<String, Map>> threadLocalCurrPageCache = new ThreadLocal<Map<String, Map>>();
private int pageSize;
private String orderBy;
private Boolean orderByAsc;
private BaseSortPagingListModelPostQueryProcessor postQueryProcessor;
public void setPostQueryProcessor(BaseSortPagingListModelPostQueryProcessor postQueryProcessor) {
this.postQueryProcessor = postQueryProcessor;
}
public BaseSortPagingListModel(int pageSize, String orderBy, Boolean orderByAsc) {
super();
this.pageSize = pageSize;
this.orderBy = orderBy;
this.orderByAsc = orderByAsc;
super.setPageSize(pageSize);
}
/**
* 构造查询条件
*/
abstract protected TC constructCriteria(String orderBy, Boolean orderByAsc);
/**
* 返回第index条数据:
* 首先从当前线程缓存查找,找到直接返回,
* 如果缓存中不存在,就调用具体子类doQuery查询当前页数据,并放入缓存
*/
@Override
public final TM getElementAt(int index) {
Map<Integer, TM> cache = this.getCurrentPageDataCache();
TM li = cache.get(index);
if (li == null) {
long start = System.currentTimeMillis(); //获取开始时间
cache.clear();
TC criteria = constructCriteria(orderBy, orderByAsc);
List result;
try {
result = doQuery(criteria, index, pageSize);
this.doPostQuery(index);
} catch (Exception e) {
logger.error(e.getMessage(), e);
result = new ArrayList();
}
int i = index;
for (Object obj : result) {
li = doTransferResultModel(obj);
cache.put(i, li);
i++;
}
long end = System.currentTimeMillis(); //获取结束时间
if (pageSize >= 100) {
logger.debug("TM getElementAt(int index) 当前页 查询耗时:" + (end - start) + " ms");
}
} else {
return li;
}
li = cache.get(index);
if (li == null) {
return handlePagingException(index);
} else {
return li;
}
}
/**
* 查询一页数据后处理
* @param index
*/
private void doPostQuery(int index) {
if (postQueryProcessor != null) {
postQueryProcessor.doPostQuery(index);
}
}
/**
*
* @param index
*/
protected TM handlePagingException(int index) {
String error = "index[" + index + "]数据找不到,total[" + this.getSize() + "]" + this;
logger.error(error);
throw new RuntimeException(error);
}
/**
* 查询当前页数据
*/
abstract protected List doQuery(TC criteria, int index, int pageSize) throws Exception;
/**
* 加工处理查询返回值,转换成前端展现需要的model格式
*/
abstract protected TM doTransferResultModel(Object obj);
/**
* 获取当前页数据缓存
*/
protected Map<Integer, TM> getCurrentPageDataCache() {
Map<String, Map> map = threadLocalCurrPageCache.get();
if (map == null) {
map = new HashMap<String, Map>();
threadLocalCurrPageCache.set(map);
}
Map<Integer, TM> cache = map.get(getChildClassCacheKey());
if (cache == null) {
cache = new HashMap<Integer, TM>();
map.put(getChildClassCacheKey(), cache);
}
return cache;
}
protected Integer getSizeCache() {
Map<String, Integer> map = threadLocalTotalSize.get();
if (map == null) {
map = new HashMap<String, Integer>();
threadLocalTotalSize.set(map);
}
return map.get(getChildClassCacheKey());
}
protected void setSizeCache(Integer size) {
Map<String, Integer> map = threadLocalTotalSize.get();
if (map == null) {
map = new HashMap<String, Integer>();
threadLocalTotalSize.set(map);
}
map.put(getChildClassCacheKey(), size);
}
/**
* memo:为避免1个页面中包含多个分页列表时数据错乱,需要在CACHE_KEY_DATA和CACHE_KEY_SIZE添加分页model对象的hashcode信息
*/
protected String getChildClassCacheKey() {
return hashCode() + "";
}
/**
* 供ZK框架调用的方法:获取总记录数
* 由于ZK框架会在处理第一次查询请求渲染页面过程中非常多次调用此方法,因此必须缓存以免影响性能!
*/
@Override
public final int getSize() {
Integer size = this.getSizeCache();// (Integer) Executions.getCurrent().getAttribute(CACHE_KEY_SIZE + getChildClassCacheKey());
if (size == null) {
TC criteria = constructCriteria(null, null);//查询总记录数时不应该orderby
try {
size = doCountTotalSize(criteria);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return 0;
}
this.setSizeCache(size);
return size;
} else {
return size;
}
}
@Override
public void sort(Comparator comparator, boolean ascending) {
if (comparator instanceof FieldComparator) {
this.orderBy = ((FieldComparator) comparator).getRawOrderBy();
this.orderByAsc = ascending;
logger.debug("order by " + orderBy + " " + (ascending ? "asc" : "desc"));
getCurrentPageDataCache().clear();//如果不清理掉threadlocal cache,不会重新按照新的sort查询
fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
}
}
@Override
public String getSortDirection(Comparator comparator) {
String rawOrderBy = ((FieldComparator) comparator).getRawOrderBy();
logger.debug(rawOrderBy);
return rawOrderBy;
}
/**
* 获取总记录数
*/
abstract protected Integer doCountTotalSize(TC criteria) throws Exception;
/**
* 清理分页状态,避免干扰下次查询
*/
public static void clearPagingState() {
threadLocalTotalSize.set(null);
threadLocalCurrPageCache.set(null);
}
}
扩展分页模板 实现分页查询的服务端逻辑
BaseSortPagingListModel模板类通过第一个泛型参数TC对查询逻辑以及回调方法abstract protected TC constructCriteria抽象了底层的实现细节,因此不会限制你用什么持久层用什么查询API,
如下代码样例不难看出是在用Hibernate的criteria API实现分页查询:
private static final int DEFAULT_PAGE_SIZE = 20;
private void doPagingQuery() {
BaseSortPagingListModel<DetachedCriteria, FiDcForecastBean> listModel= preparePagingListModel(DEFAULT_PAGE_SIZE);
renderListboxPage(forecastListbox, 0, listModel);
}
@SuppressWarnings("serial")
private BaseSortPagingListModel<DetachedCriteria, FiDcForecastBean> preparePagingListModel(int pageSize) {
return new BaseSortPagingListModel<DetachedCriteria, FiDcForecastBean>(pageSize , sortColum,
sortAscFlag) {
@Override
protected DetachedCriteria constructCriteria(String orderBy, Boolean asc) {
return prepareDc(forecastTaskTypeList, queryForm);
}
@Override
protected Integer doCountTotalSize(DetachedCriteria dc) throws Exception {
return HibernateUtil.countRecordsByCriteria(dc, "id");
}
@Override
protected List doQuery(DetachedCriteria dc, int index, int pageSize) throws Exception {
return HibernateUtil.findByCriteria(dc, index, pageSize);
}
@Override
protected FiDcForecastBean doTransferResultModel(Object item) {
return (FiDcForecastBean) item;
}
};
}
public static void renderListboxPage(Listbox lb, int currActivePage, BaseSortPagingListModel pagingListModel) {
lb.getPaginal().setDetailed(true);
lb.getPaginal().setPageSize(pagingListModel.getPageSize());
int size = pagingListModel.getSize();
lb.getPaginal().setTotalSize(size);
lb.setModel(pagingListModel);
lb.getPaginal().setActivePage(currActivePage);
lb.invalidate();
}
页面
<listbox id="forecastListbox" hflex="true" emptyMessage="no data" mold="paging"
nonselectableTags="*" multiple="false" checkmark="false">
<listhead。。。。。。
<template name="model">
<listitem>
<custom-attributes data="${each}" />
<listcell label="${forEachStatus.index+1}" />
<listcell label="${each.po}" />
<listcell label="${each.colour}" />
</listitem>
</template>
</listbox>