在你需要处理大量数据时你应该考虑使用eXtremeTable的Limit特性。Limit这个名字来自MySQL的limit
命令,Limit接口的目的就是如何对表的结果集进行limit处理。Limit实现知道当排序、过滤、分页、导出时,用户如何与表互相作用。有了这些信息你
将能够使用可能是最有效的方式显示正确的过滤、排序后的请求页面。
为了示范Limit特性,我将要做的工作将分解为JSP、Controller、Service和DAO。这示范了一种使用分层的方式来处理
Limit。你可以根据自己的需要来增加或减少层。本示例也使用了Spring框架来重新得到使用Spring的JDBC取得的数据,因此你的代码看起来可能有点不同。eXtremeTable的一个特点就是不依赖任何框架和容器。
为了使用Limit特性,eXtremeTable需要使用limit特定的RetrieveRowsCallback、
FilterRowsCallback和SortRowsCallback接口。eXtremeComponents提供了每个接口的一个实现,可以简单地通过设置每个属性值为limit来简单来使用。
<ec:table
items="presidents"
retrieveRowsCallback="limit"
filterRowsCallback="limit"
sortRowsCallback="limit"
view="limit"
>
...
另外视图属性参照一个名为limit的定制视图。这是一个简单修改默认eXtremeTable视图,不包含最后页工具条的实现。这仅仅关系到你是否能取得确切需要的行。
一些数据库例如Oracle和MySQL都提供了一种得到确定行的特性,但是,其他的数据库例如:Sybase没有提供特性。在我的示例中我考虑最坏的情况你的数据库不支持这种特性。
即使你的数据库不提供取得特定行的特性,当你考虑用户如何和表协同工作时,Limit仍然非常有意义。用户通常会对一些数据进行排序、过滤和分页。
这个例子中15条数据构成一页,第一页需要15条数据,第二页需要30条数据,第三页需要45条数据,以此类推。在经过一段时间分页后,他们常常使用过滤来提炼数据。
即使他们不这样做,他们也必须在此之前对大量的数据进行分页,这将影响效率。当然如果允许用户点击最后页,那么所有的数据都将被取出,这将非常影响效率。
提示:Spring框架的Controller和Struts框架的Action非常相像。
controller首先需要创建一个Limit。为了完成这个你需要得到一些关于Context和LimitFactory的帮助。
Context context = new HttpServletRequestContext(request);
LimitFactory limitFactory = new TableLimitFactory(context);
Limit limit = new TableLimit(limitFactory);
Context是一个处理取得属性的接口,LimitFactory使用Context来找出用户如何和eXtremeTable交互。
然后Limit使用LimitFactory来组装自己。
为了初始化Limit,它将包含所有的有用的信息。这些信息包括数据将被如何排序和过滤,哪一页将被显示和是否允许被导出。
然而,Limit仍然需要得到行的信息,这样正确的信息页面才能被显示给用户。行信息包括开始行、结束行、当前显示行。
controller必须从service得到这些信息,而Service将从dao中得到这些信息。这里我只给出Controller端的代码。
int totalRows = presidentsService.getTotalPresidents(limit.getFilterSet(), limit.isExported());
limit.setRowAttributes(totalRows, defaultRowsDisplayed);
limit需要得到所有的行来得到行的信息。service需要知道那些被过滤,不管这些数据是否要导出。为了设置行信息,默认的一页显示的行数需要被设置。
这可以通过对TableTag的rowsDisplayed属性设置一个确定的数值来实现。
现在我们只需要从services得到Collection数据。
Collection presidents = presidentsService.getPresidents(limit.getFilterSet(), limit.getSort(), limit.getRowEnd());
因为limit已经包含所有信息,这将十分容易。所有需要做的就是传入过滤器,排序和最后行的信息。
最后要做的是将Collections和totalRow这些信息传送回JSP以便eXtremeTable知道如何显示这些信息。
request.setAttribute("presidents", presidents);
request.setAttribute("totalRows", new Integer(totalRows));
service需要和dao进行交互来得到总行数和Collection。
controller需要到第一条信息就是总行数。
public int getTotalPresidents(FilterSet filterSet, boolean isExported) {
String totalQuery = presidentsDao.getTotalPresidentsQuery();
String modTotalQuery = filterQuery(filterSet, totalQuery);
int totalRows = presidentsDao.getTotalPresidents(modTotalQuery);
if (isExported && totalRows > maxExportRows) {
totalRows = maxExportRows;
}
return totalRows;
}
service和dao一起来过滤结果集,它的工作方式是在Where语句后面增加更多的AND语句来修改查询字符串。为此,你需要和Limit
FilterSet一起工作。
FilterSet是一个过滤器对象数组,一个过滤器包括一个bean
property和这个过滤器的值。或者,简单的说就是用户想要过滤的行和他们输入的值。这使得它非常容易交互。service只需要迭代所有的
FilterSet并调用dao来拼接查询语句。(译者注:过滤的实现方式是:在Where后面增加And语句来改变查询语句以达到对数据进行过滤的效果)
private String filterQuery(FilterSet filterSet, String query) {
if (!filterSet.isFiltered() || filterSet.isCleared()) {
return query;
}
Filter filters[] = filterSet.getFilters();
for (int i = 0; i < filters.length; i++) {
Filter filter = filters[i];
String property = filter.getProperty();
String value = filter.getValue();
query = presidentsDao.filterQuery(query, property, value);
}
return query;
}
query修改包括了filter信息,总行数。在一些情况下这就足够,但是当用户导出数据时仍然存在一个潜在的问题。为了保持高效
service不允许导出超出一个最大行数的数据。
controller需要到第二条信息就是Collection。
public Collection getPresidents(FilterSet filterSet, Sort sort, int rowEnd) {
String patientsQuery = presidentsDao.getPresidentsQuery();
String modPatientsQuery = filterQuery(filterSet, patientsQuery);
modPatientsQuery = sortQuery(sort, modPatientsQuery);
modPatientsQuery = presidentsDao.limitQuery(rowEnd, modPatientsQuery);
return presidentsDao.getPresidents(modPatientsQuery);
}
和前面一样,service和dao一起来过滤结果集。
另外query字符串需要扩展ORDER BY语句以便数据按照正确的方式进行排序。Sort包含一个bean property和
sortOrder值(正序还是逆序)。service仅仅需要使用Sort来调用dao。
private String sortQuery(Sort sort, String query) {
if (!sort.isSorted()) {
String defaultSortOrder = presidentsDao.getDefaultSortOrder();
if (StringUtils.isNotBlank(defaultSortOrder)) {
return query + defaultSortOrder;
}
return query;
}
String property = sort.getProperty();
String sortOrder = sort.getSortOrder();
return presidentsDao.sortQuery(query, property, sortOrder);
}
query字符串最后需要的修改就是增加数据库特别的指令来limit将要被返回的结果集。这就是limitQuery()
方法的作用。
dao为service负责底层数据工作。
为了真正理解dao,query字符串需要被展示。
这就是得到数据的presidents query字符串:
private final static String presidentsQuery =
" SELECT " +
" president_id presidentId, " +
" first_name firstName, " +
" last_name lastName, " +
" nick_name nickName, " +
" concat(first_name, ' ',last_name) fullName, " +
" term, " +
" born, " +
" died, " +
" education, " +
" career, " +
" political_party politicalParty " +
" FROM presidents ";
这是得到总行数的query字符串:
private final static String totalPresidentsQuery =
" SELECT count(*) FROM presidents ";
1.4.2. Filter 和 Sort Query 字符串
两个最有趣的方法就是过滤和排序。
filter看起来像这样:
public String filterQuery(String query, String property, String value) {
StringBuffer result = new StringBuffer(query);
if (query.indexOf("WHERE") == -1) {
result.append(" WHERE 1 = 1 "); //stub WHERE clause so can just append AND clause
}
if (property.equals("fullName")) {
result.append(" AND concat(first_name, ' ',last_name) like '%" + value + "%'");
} else if (property.equals("nickName")) {
result.append(" AND nick_name like '%" + value + "%'");
} else {
result.append(" AND " + property + " like '%" + value + "%'");
}
return result.toString();
}
filterQuery()方法需要增加正确的AND语句到query字符串。
sort看起来非常类似:
public String sortQuery(String query, String property, String sortOrder) {
StringBuffer result = new StringBuffer(query + " ORDER BY ");
if (property.equals("fullName")) {
result.append("concat(first_name, ' ',last_name) " + sortOrder);
} else {
result.append(property + " " + sortOrder);
}
return result.toString();
}
sortQuery()方法需要增加正确的ORDER BY语句到query字符串。
1.4.3. Limit Query String
现在query字符串修改能够正确的进行filter和sort,它还需要修改以便只取页面显示相关的数据。MySQL为s the
limit命令。
public String limitQuery(int rowEnd, String query) {
return query + " limit " + rowEnd;
}
service需要的唯一东西就是:总行数和Collection。
public Collection getPresidents(final String query) {
return jdbcTemplate.query(query, new ResultReader() {
List results = new ArrayList();
public List getResults() {
return results;
}
public void processRow(ResultSet rs)
throws SQLException {
President president = new President();
president.setPresidentId(new Integer(rs.getInt("presidentId")));
president.setFirstName(rs.getString("firstName"));
president.setLastName(rs.getString("lastName"));
president.setNickName(rs.getString("nickName"));
president.setFullName(rs.getString("fullName"));
president.setTerm(rs.getString("term"));
president.setBorn(rs.getDate("born"));
president.setDied(rs.getDate("died"));
president.setEducation(rs.getString("education"));
president.setCareer(rs.getString("career"));
president.setPoliticalParty(rs.getString("politicalParty"));
results.add(president);
}
});
}
public int getTotalPresidents(final String query) {
return jdbcTemplate.queryForInt(query);
}
ResultReader是一个帮助处理JDBC查询的Spring特殊类,作为一个callback来处理JDBC
ResultSet。jdbcTemplate是对JDBC连接的抽象。
最后,这是service需要的默认sort顺序:
public String getDefaultSortOrder() {
return " ORDER BY concat(first_name, ' ', last_name) ";
}