- 用eXtremeTable标签实现自动分页
- 用oscache缓存jsp,提高性能
第一.自己实现一个工具类PageBean完成所有分页工作.
本分页实现概览:Struts + hibernate
PageBean负责两部分内容,一是要在页面显示的业务信息,是一个ArrayList;另一个是逻辑控制信息,诸如是否有下一页,上一页等等.
PageBean代码如下:
public
class
PageBean {
int
currentPage
=
1
;
//
当前页:Action控制
int
totalPages
=
0
;
//
总页数 :自己运算
int
pageRecorders
=
5
;
//
每页记录数,默认为5,可以在初始化的时候修改
//
总数据数
int
pageStartRow
=
0
;
//
每页的起始数
int
pageEndRow
=
0
;
//
每页显示数据的终止数
boolean
hasNextPage
=
false
;
//
是否有下一页:自己运算
boolean
hasPreviousPage
=
false
;
//
是否有前一页 :自己运算
List objList
=
new
ArrayList();
//
存放欲展示的对象列表
int
totalRows;
//
总记录数,由底层service提供
//
是否有上一页
public
boolean
isHasPreviousPage() {
return
(currentPage
>
1
?
true
:
false
);
}
//
共有多少页,service只提供有多少条记录,多少页数由PageBean自己运算
public
int
getTotalPages() {
return
(totalRows
/
pageRecorders
==
0
?
totalRows
/
pageRecorders:totalRows
/
pageRecorders
+
1
);
}
public
int
getCurrentPage() {
return
currentPage;
}
public
int
getPageEndRow() {
return
pageEndRow;
}
//
是否有下一页
public
boolean
isHasNextPage() {
return
(currentPage
<
this
.getTotalPages()
?
true
:
false
);
}
public
int
getTotalRows() {
return
totalRows;
}
public
int
getPageStartRow() {
return
pageStartRow;
}
public
int
getPageRecorders() {
return
pageRecorders;
}
public
void
setObjList(List objList) {
this
.objList
=
objList;
}
public
void
setHasPreviousPage(
boolean
hasPreviousPage) {
this
.hasPreviousPage
=
hasPreviousPage;
}
public
void
setTotalPages(
int
totalPages) {
this
.totalPages
=
totalPages;
}
public
void
setCurrentPage(
int
currentPage) {
this
.currentPage
=
currentPage;
}
public
void
setPageEndRow(
int
pageEndRow) {
this
.pageEndRow
=
pageEndRow;
}
public
void
setHasNextPage(
boolean
hasNextPage) {
this
.hasNextPage
=
hasNextPage;
}
public
void
setTotalRows(
int
totalRows) {
this
.totalRows
=
totalRows;
}
public
void
setPageStartRow(
int
pageStartRow) {
this
.pageStartRow
=
pageStartRow;
}
public
void
setPageRecorders(
int
pageRecorders) {
this
.pageRecorders
=
pageRecorders;
}
public
List getObjList() {
return
objList;
}
public
PageBean() {}
public
void
description() {
String description
=
"
共有数据数:
"
+
this
.getTotalRows()
+
"
共有页数:
"
+
this
.getTotalPages()
+
"
当前页数为:
"
+
this
.getCurrentPage()
+
"
是否有前一页:
"
+
this
.isHasPreviousPage()
+
"
是否有下一页:
"
+
this
.isHasNextPage()
+
"
开始行数:
"
+
this
.getPageStartRow()
+
"
终止行数:
"
+
this
.getPageEndRow();
System.out.println(description);
}
}
注意,我没有在PageBean里放具体的业务逻辑,诸如getBooks()等,目的很简单,具有通用性,业务逻辑由另一个业务类实现BookService,BookService获得的业务数据都放在了PageBean的ArrayList里.
BookService代码如下:
public
class
BookService {
private
static
Logger log
=
Logger.getLogger(BookService.
class
.getName());
public
BookService() {
}
/**
* 获得book列表
*
@param
pageBean PageBean:返回的对象存在pageBean里
*/
public
static
void
getBooks(PageBean pageBean) {
String infoSql
=
"
from Book
"
;
//
获得业务信息
String countSql
=
"
select count(*) from Book
"
;
//
获得控制信息
Session session
=
null
;
try
{
session
=
DBUtil.currentSession();
Query query
=
session.createQuery(infoSql);
query.setFirstResult((pageBean.getCurrentPage()
-
1
)
*
pageBean.getPageRecorders());
//
起始页
query.setMaxResults(pageBean.getPageRecorders());
//
每页记录数
pageBean.getObjList().clear();
for
(Iterator it
=
query.iterate(); it.hasNext(); ) {
Book po
=
(Book)it.next();
BookVo vo
=
new
BookVo();
BeanUtils.copyProperties(vo,po);
pageBean.getObjList().add(vo);
}
session
=
DBUtil.currentSession();
query
=
session.createQuery(countSql);
int
totalRecords
=
((Integer)query.list().get(
0
)).intValue();
pageBean.setTotalRows(totalRecords);
}
catch
(Exception e) {
e.printStackTrace();
System.out.println(
"
数据库异常
"
+
e.toString());
}
finally
{
try
{
if
(
null
!=
session) {
session.close();
}
}
catch
(HibernateException ex) {
ex.printStackTrace();
}
}
}
}
在Struts的Action中调用service,返回一个PageBean给展示页面
Action代码如下:
1
public
class
PageListAction
extends
Action {
2
3
public
PageListAction() {}
4
5
ArrayList arrayList
=
new
ArrayList();
6
7
public
ActionForward execute(ActionMapping mapping,ActionForm form,HttpServletRequest request,HttpServletResponse response)
throws
Exception {
8
String action;
9
PageBean pageBean
=
null
;
10
action
=
request.getParameter(
"
action
"
);
11
if
(action
==
null
||
action.equals(
"
null
"
)) {
//
第一次读取数据
12
pageBean
=
new
PageBean();
13
}
else
{
//
用户选择上一页或者下一页
14
if
(action
==
"
nextPage
"
||
action.equals(
"
nextPage
"
)) {
15
pageBean
=
(PageBean)request.getSession().getAttribute(
"
pageBean
"
);
16
pageBean.setCurrentPage(pageBean.getCurrentPage()
+
1
);
17
}
else
if
(action
==
"
previousPage
"
||
action.equals(
"
previousPage
"
)) {
18
pageBean
=
(PageBean)request.getSession().getAttribute(
"
pageBean
"
);
19
pageBean.setCurrentPage(pageBean.getCurrentPage()
-
1
);
20
}
else
if
(action
==
"
targetPage
"
||
action.equals(
"
targetPage
"
)){
//
指定页
21
pageBean
=
(PageBean)request.getSession().getAttribute(
"
pageBean
"
);
22
System.out.println(
"
targetPage=
"
+
request.getParameter(
"
targetPage
"
));
23
//
这里根据需要可以对填写的目标页进行判断,不要大于最大页数[此处省略]
24
pageBean.setCurrentPage(Integer.parseInt(request.getParameter(
"
targetPage
"
)));
25
}
26
}
27
if
(
null
==
pageBean)
throw
new
Exception(
"
获得PageBean异常
"
);
28
BookService service
=
new
BookService();
29
service.getBooks(pageBean);
30
pageBean.description();
31
request.getSession().setAttribute(
"
pageBean
"
,pageBean);
32
request.setAttribute(
"
result
"
,pageBean.getObjList());
33
return
(mapping.findForward(
"
success
"
));
34
}
35
}
在本Action中判断了可能出现的三种情况:
- 用户选择了"上一页"
- 用户选择了"下一页"
- 用户手工输入了指定的某一页
这里有点感觉不爽的是必须hard coding,但是不这么做感觉暂时也想不出什么好的办法来,毕竟一个PageBean不可能封装所有的细节,如果你有更好的方式请指点哦 :)
好了,到了我们呼之欲出的展示页面了 :)
show.jsp代码如下
<%
@ taglib uri
=
"
/WEB-INF/struts-logic.tld
"
prefix
=
"
logic
"
%>
<%
@ taglib uri
=
"
/WEB-INF/struts-bean.tld
"
prefix
=
"
bean
"
%>
<%
@ taglib uri
=
"
/WEB-INF/struts-html.tld
"
prefix
=
"
html
"
%>
<%
@ page contentType
=
"
text/html; charset=gb2312
"
language
=
"
java
"
%>
<
html:html
locale
="true"
>
<
head
>
<
meta
http-equiv
="Content-Type"
content
="text/html; charset=gb2312"
>
<
script
language
="javaScript"
>
function
go(){
try
{
var
targetValue
=
document.getElementById(
"
targetPage
"
).value;
parseInt(targetValue);
alert(targetValue);
}
catch
(e){
alert(
"
请正确填写目标页
"
);
return
;
}
if
(targetValue
==
null
||
targetValue
==
''){
alert(
"
请填写目标页
"
);
return
;
}
window.location
=
"
/fuck/pageList.do?action=targetPage&targetPage=
"
+
targetValue;
}
</
script
>
</
head
>
<
body
>
<
logic:present
name
="pageBean"
>
共有数据总数
<
bean:write
name
="pageBean"
property
="totalRows"
/>
;
共分
<
bean:write
name
="pageBean"
property
="totalPages"
/>
页,
当前是第
<
bean:write
name
="pageBean"
property
="currentPage"
/>
页
</
logic:present
>
<
table
border
="1"
>
<
tr
><
th
>
书名
</
th
><
th
>
作者
</
th
><
th
>
价格
</
th
></
tr
>
<
logic:present
name
="result"
>
<
logic:iterate
id
="book"
name
="result"
>
<
logic:present
name
="book"
>
<
tr
>
<
td
><
bean:write
name
="book"
property
="name"
/></
td
>
<
td
>
<
bean:write
name
="book"
property
="author"
/></
td
>
<
td
><
bean:write
name
="book"
property
="price"
/></
td
>
/
tr
>
</
logic:present
>
</
logic:iterate
>
</
logic:present
>
</
table
>
<
logic:present
name
="pageBean"
>
<
logic:equal
name
="pageBean"
property
="hasNextPage"
value
="true"
>
<
html:link
page
="/pageList.do?action=nextPage"
>
nextPage
</
html:link
>
</
logic:equal
>
<
logic:equal
name
="pageBean"
property
="hasPreviousPage"
value
="true"
>
<
html:link
page
="/pageList.do?action=previousPage"
>
PreviousPage
</
html:link
>
</
logic:equal
>
<
input
type
="text"
name
="targetPage"
id
="targetPage"
/>
<
input
type
="button"
value
="go!"
size
="2"
onclick
="go();"
/>
</
logic:present
>
</
body
>
</
html:html
>
是否有上一页或者下一页,全部根据PageBean里的逻辑值动态判断.
这个页面没什么可说的,你可以根据自己的情况调整就OK了
第二. 用eXtremeTable标签实现自动分页
上面的方案大家已经看出来了,实际上是每一次用户点击一个页面都会查询数据库,这可以算是既是优点也是缺点,优点是数据库不用一次查询出所有的数据,在高数据量的情况下尤其如此,缺点就是和数据库的交互次数有点多了,不过这个完全看你的业务策略了,如果用户大多数情况下就是看没几条的记录,你又何必把全部数据给他取出来呢? 当然,在这里我们就说说一次取出全部数据,然后让标签帮助我们自动分页,终于可以偷懒了,你所要做的仅仅是取出所需要的业务数据而已,其他的就交给eXtremeTable标签来完成就OK了.
eXtremeTable标签的下载,安装和文档请参看
官方网站
- BookService增加一个方法[显示所有记录]
public
static
List getBooks() {
log.debug(
"
execute getBooks method!
"
);
String infoSql
=
"
from Book
"
;
//
获得业务信息
Session session
=
null
;
List rtnList
=
new
ArrayList();
try
{
session
=
DBUtil.currentSession();
Query query
=
session.createQuery(infoSql);
for
(Iterator it
=
query.iterate(); it.hasNext(); ) {
Book po
=
(Book) it.next();
BookVo vo
=
new
BookVo();
BeanUtils.copyProperties(vo,po);
log.debug(
"
vo = [
"
+
vo
+
"
]
"
);
rtnList.add(vo);
}
}
catch
(Exception e) {
e.printStackTrace();
System.out.println(
"
数据库异常
"
+
e.toString());
}
finally
{
try
{
if
(
null
!=
session) {
session.close();
}
}
catch
(HibernateException ex) {
ex.printStackTrace();
}
}
return
rtnList;
}
代码如下:
public
class
PageListWithTagAction
extends
Action {
public
ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
BookService service
=
new
BookService();
//我这里把数据放到session中,相当于做了缓存,根据你的业务策略也可以不用这么做
//
如果session中没有,则从数据库中查询
if
(
null
==
httpServletRequest.getSession().getAttribute(
"
result
"
)){
List result
=
service.getBooks();
httpServletRequest.getSession().setAttribute(
"
result
"
,result);
}
return
(actionMapping.findForward(
"
success
"
));
}
}
- jsp[show.jsp代码如下,留心里面的标签使用方法]
<
body
bgcolor
="#ffffff"
>
<
ec:table
tableId
="fuck"
items
="result"
action
="${pageContext.request.contextPath}/pageListWithTag.do"
imagePath
="${pageContext.request.contextPath}/jsp/images/table/*.gif"
title
="Books"
width
="60%"
rowsDisplayed
="5"
locale
="zh_CN"
cellpadding
="1"
cellspacing
="1"
border
="1"
method
="post"
showPagination
="false"
filterable
="false"
>
<
ec:exportXls
fileName
="Book.xls"
tooltip
="导出Excel"
>
</
ec:exportXls
>
<
ec:exportPdf
fileName
="Book.pdf"
tooltip
="导出pdf"
headerColor
="blue"
headerBackgroundColor
="red"
headerTitle
="Book"
>
</
ec:exportPdf
>
<
ec:row
highlightRow
="true"
>
<
ec:column
property
="name"
title
="书名"
>
<
a
href
="${pageContext.request.contextPath}/bookDetail.do?bookID=${fuck.id}&bookName=${fuck.name}"
>
${fuck.name}
</
a
>
</
ec:column
>
<
ec:column
property
="author"
title
="作者"
>
<!--
here can't use 'result.author'
-->
${fuck.author}
</
ec:column
>
<
ec:column
property
="price"
title
="价格"
cell
="currency"
format
="$###,###,##0.00"
/>
<
ec:column
property
="date"
title
="日期"
cell
="date"
format
="yyyy年MM月dd日"
>
</
ec:column
>
</
ec:row
>
</
ec:table
>
</
body
>
感觉如何?你不用再画你的页面了,不用再画table了,这点我特别喜欢,因为我自己画的东西都比较难看,毕竟我的美工功夫不够 :( "自从有了eXtremeTable吃嘛嘛香" :)
具体的标签使用方法请参考官方文档的Manual,说明还是比较详细的.
第三.用oscache缓存你的页面
为了提高页面的速度我们想了很多办法,比如 预编译的办法,以及把你常用的数据放到内存里,是的,除了用内存我们还能想到用什么办法呢,恩,我想以后cpu的缓存也特别大的话我们的下一个方案肯定就是把数据全部放到cpu里得了,哈哈,展望一下 :)
言归正传, oscache的广告我就不做了,差不多地球人都知道了,这里仅仅提供了一个想法,我想这确实是一个不错的方案,它提供了2个途径使用,一是通过tag的方式,可能也是用的最多的方式,另一个便是调用API,当然就可以在任何想调用的地方使用了.另外还有一个特别不错的功能就是有策略的刷新数据.这是一个useful的方式,比如你做了增删改操作那么数据库的数据已经发生变化了,你可以通知缓存来更新数据,方式是通过key或者group.
下面是几个摘自FAQ里的几个常用Example
<
cache:cache
time
="600"
>
<%
=
myBean.getTitle()
%>
</
cache:cache
>
<
cache:cache
key
="foobar"
scope
="session"
>
<%
=
myBean.getTitle()
%>
</
cache:cache
>
<
cache:cache
>
<%
try {
%>
<%
=
myBean.getTitle()
%>
>
<%
} catch (Exception e) {
%>
<%
application.log(
"
Exception occurred in myBean.getTitle():
"
+
e);
%>
<
cache:usecached
/>
<%
}
%>
</
cache:cache
>
上面的Example3可以实现在数据库当机的情况下从缓存里读取数据展示,显得更加友好
不知你注意到了没有,在oscache官方展示的例子里的jsp都有一个我称之为毛病的东西,或者说是困惑,那就是都用了
<%
=
.
%>
这种方式,感觉有点别扭,毕竟这种使用方式对于别人我不知道,反正对于我来说用的比较少,在以前的使用中我记得只有使用xml数据岛的时候用过这种方式,其他情况下很少用=的方式来打印出一些动态的数据,更常见的可能是如下这样的情况:
<
cache:cache
key
="dispInfo"
groups
="disInfo"
time
="1200"
>
<%
BookService service
=
new
BookService();
List list
=
service.getBooks();
request.setAttribute(
"
list
"
,list);
%>
</
cache:cache
>
<
c:forEach
items
="${list}"
var
="item"
>
<
c:out
value
="${item.id}"
/>
==
<
c:out
value
="${item.name}"
/><
br
/>
</
c:forEach
>
不幸的是,这个一厢情愿的做法并不被oscache支持,除了第一次能够显示数据,下一次就显示不了了.也许oscache所谓的缓存jsp代码就是指缓存诸如<%=%>的方式才是jsp代码,其他的java型的就不被支持了?可能是理解不够,继续研究吧,希望有知道的不吝赐教 :)
恩,就是这么多,关于页面的缓存和分页以及标签.欢迎有这方面的更好的想法来交流.
本人不才,可能有很多地方理解不够深入,见笑了 :)