在你需要處理大量數(shù)據(jù)時你應(yīng)該考慮使用eXtremeTable的Limit特性。Limit這個名字來自MySQL的limit
命令,Limit接口的目的就是如何對表的結(jié)果集進行l(wèi)imit處理。Limit實現(xiàn)知道當(dāng)排序、過濾、分頁、導(dǎo)出時,用戶如何與表互相作用。有了這些信息你
將能夠使用可能是最有效的方式顯示正確的過濾、排序后的請求頁面。
為了示范Limit特性,我將要做的工作將分解為JSP、Controller、Service和DAO。這示范了一種使用分層的方式來處理
Limit。你可以根據(jù)自己的需要來增加或減少層。本示例也使用了Spring框架來重新得到使用Spring的JDBC取得的數(shù)據(jù),因此你的代碼看起來可能有點不同。eXtremeTable的一個特點就是不依賴任何框架和容器。
為了使用Limit特性,eXtremeTable需要使用limit特定的RetrieveRowsCallback、
FilterRowsCallback和SortRowsCallback接口。eXtremeComponents提供了每個接口的一個實現(xiàn),可以簡單地通過設(shè)置每個屬性值為limit來簡單來使用。
<ec:table
items="presidents"
retrieveRowsCallback="limit"
filterRowsCallback="limit"
sortRowsCallback="limit"
view="limit"
>
...
另外視圖屬性參照一個名為limit的定制視圖。這是一個簡單修改默認eXtremeTable視圖,不包含最后頁工具條的實現(xiàn)。這僅僅關(guān)系到你是否能取得確切需要的行。
一些數(shù)據(jù)庫例如Oracle和MySQL都提供了一種得到確定行的特性,但是,其他的數(shù)據(jù)庫例如:Sybase沒有提供特性。在我的示例中我考慮最壞的情況你的數(shù)據(jù)庫不支持這種特性。
即使你的數(shù)據(jù)庫不提供取得特定行的特性,當(dāng)你考慮用戶如何和表協(xié)同工作時,Limit仍然非常有意義。用戶通常會對一些數(shù)據(jù)進行排序、過濾和分頁。
這個例子中15條數(shù)據(jù)構(gòu)成一頁,第一頁需要15條數(shù)據(jù),第二頁需要30條數(shù)據(jù),第三頁需要45條數(shù)據(jù),以此類推。在經(jīng)過一段時間分頁后,他們常常使用過濾來提煉數(shù)據(jù)。
即使他們不這樣做,他們也必須在此之前對大量的數(shù)據(jù)進行分頁,這將影響效率。當(dāng)然如果允許用戶點擊最后頁,那么所有的數(shù)據(jù)都將被取出,這將非常影響效率。
提示:Spring框架的Controller和Struts框架的Action非常相像。
controller首先需要創(chuàng)建一個Limit。為了完成這個你需要得到一些關(guān)于Context和LimitFactory的幫助。
Context context = new HttpServletRequestContext(request);
LimitFactory limitFactory = new TableLimitFactory(context);
Limit limit = new TableLimit(limitFactory);
Context是一個處理取得屬性的接口,LimitFactory使用Context來找出用戶如何和eXtremeTable交互。
然后Limit使用LimitFactory來組裝自己。
為了初始化Limit,它將包含所有的有用的信息。這些信息包括數(shù)據(jù)將被如何排序和過濾,哪一頁將被顯示和是否允許被導(dǎo)出。
然而,Limit仍然需要得到行的信息,這樣正確的信息頁面才能被顯示給用戶。行信息包括開始行、結(jié)束行、當(dāng)前顯示行。
controller必須從service得到這些信息,而Service將從dao中得到這些信息。這里我只給出Controller端的代碼。
int totalRows = presidentsService.getTotalPresidents(limit.getFilterSet(), limit.isExported());
limit.setRowAttributes(totalRows, defaultRowsDisplayed);
limit需要得到所有的行來得到行的信息。service需要知道那些被過濾,不管這些數(shù)據(jù)是否要導(dǎo)出。為了設(shè)置行信息,默認的一頁顯示的行數(shù)需要被設(shè)置。
這可以通過對TableTag的rowsDisplayed屬性設(shè)置一個確定的數(shù)值來實現(xiàn)。
現(xiàn)在我們只需要從services得到Collection數(shù)據(jù)。
Collection presidents = presidentsService.getPresidents(limit.getFilterSet(), limit.getSort(), limit.getRowEnd());
因為limit已經(jīng)包含所有信息,這將十分容易。所有需要做的就是傳入過濾器,排序和最后行的信息。
最后要做的是將Collections和totalRow這些信息傳送回JSP以便eXtremeTable知道如何顯示這些信息。
request.setAttribute("presidents", presidents);
request.setAttribute("totalRows", new Integer(totalRows));
service需要和dao進行交互來得到總行數(shù)和Collection。
controller需要到第一條信息就是總行數(shù)。
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一起來過濾結(jié)果集,它的工作方式是在Where語句后面增加更多的AND語句來修改查詢字符串。為此,你需要和Limit
FilterSet一起工作。
FilterSet是一個過濾器對象數(shù)組,一個過濾器包括一個bean
property和這個過濾器的值。或者,簡單的說就是用戶想要過濾的行和他們輸入的值。這使得它非常容易交互。service只需要迭代所有的
FilterSet并調(diào)用dao來拼接查詢語句。(譯者注:過濾的實現(xiàn)方式是:在Where后面增加And語句來改變查詢語句以達到對數(shù)據(jù)進行過濾的效果)
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信息,總行數(shù)。在一些情況下這就足夠,但是當(dāng)用戶導(dǎo)出數(shù)據(jù)時仍然存在一個潛在的問題。為了保持高效
service不允許導(dǎo)出超出一個最大行數(shù)的數(shù)據(jù)。
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一起來過濾結(jié)果集。
另外query字符串需要擴展ORDER BY語句以便數(shù)據(jù)按照正確的方式進行排序。Sort包含一個bean property和
sortOrder值(正序還是逆序)。service僅僅需要使用Sort來調(diào)用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字符串最后需要的修改就是增加數(shù)據(jù)庫特別的指令來limit將要被返回的結(jié)果集。這就是limitQuery()
方法的作用。
dao為service負責(zé)底層數(shù)據(jù)工作。
為了真正理解dao,query字符串需要被展示。
這就是得到數(shù)據(jù)的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 ";
這是得到總行數(shù)的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
現(xiàn)在query字符串修改能夠正確的進行filter和sort,它還需要修改以便只取頁面顯示相關(guān)的數(shù)據(jù)。MySQL為s the
limit命令。
public String limitQuery(int rowEnd, String query) {
return query + " limit " + rowEnd;
}
1.4.4. 取回總行數(shù)和Collection.
service需要的唯一東西就是:總行數(shù)和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) ";
}