摘要:
JSP 中的分页问题, 涉及到后台数据库, 业务逻辑层, 表现层的各个方面的知识, 这个问题如果搞清楚了, 对三层的开发结构就有了更为理性的认识.
这里我们就看一看大家是怎么解决这个问题的.
skying:
用JSP查询数据库的东西出来, 怎么实现分页显示啊, 一定要写个bean吗?
(点评: 是这样的, 如果不使用 bean 的话, 整个的设计和效率不会好.
由于原来的问题找不到了, 所以下面的回答可能有些偏, 不过大概能表达出意思来.)
gty:
有application server,怎么能没有web server呢:)
倒到容器里也不太好,太耗费资源了。
我后来想了一个方法,只是感觉不太舒服,请帮我看看有那儿不对(或哪些情况下问题不能解决)
解决方案:
假设:所有的查询结果都是按一定顺序显示的举例:用户查询自己订单的case中,查询出的订单纪录按
时间倒序排列 (最近的订单排在最前面),一个html页面显示10条纪录,第一页的最后一条纪录的时间是10-24-2000。
要求:查询第11-20条纪录,显示在下一个页面
解决:"select * from tblOrder where userid='guest' and submitDate<10-24-2000 order by submitDate DESC"
这样,产生一个ResultSet。然后从ResultSet中取出10条纪录,转化为serializable object(本例中为order),放到一个Collection(Vector or Arraylist)里,送回到Web tier,交给JSP页面显示。
这种方法必须要求查询结果按顺序显示,而且查询条件是Web层可知的。
问题:如果多条订单的submitDate是10-24-2000,会漏掉很多纪录,怎么办?
欢迎大家讨论!?
(点评:
把查出来的结果保存起来, 下次取数据的时候可以直接取, 就相当于 cache, 是一种很常用的方法, 但是有其局限性, 当数据更新较快的时候, 就不能采用.这个和 cache 还不一样, cache 在没有命中的时候, 会去数据库中取, 而查出来后不再查了, 所以只能在以前查出来的结果中取, 这也就是是其局限性
)
alou:
我觉得不需要<10-24-2000这个条件。
在第一次查询的时候就全部查出来,序列化到一个collection之类的东西去。
只要这东西能够跨页保存,就可以很容易的跨页浏览。
gty:
问题是如果合符条件的记录很多(100?)的话,这个Collection就太大了。
如果对数据的实时性要求不高,例如产品目录,所有用户共享这个Collection就非常好了。
但如果数据老是变化,例如订单,我觉得还是从数据库老老实实的取比较好。
你认为呢?
(点评: 这个问题提的好, 大量的数据都存起来不太好. 是的, 当数据变化不大的时候, 在 application 范围内共享, 这个效率是很高的, 但是, 当数据更新较快时, 保存下来的做法不足取, 有可能最新的数据就没有取到, 还是每次都查询的好. )
alou:
这是个可权衡的方案。
如果是动态比较大的数据,可能会有一些可以取代日期的域如流水编号等作为重新查询的条件。
(点评: 这里有些偏离原来的意思了, 原来是只查询一次, 而这里却说重新查询. 其实每次查询未尝不可, 也是有条件的, 查询速度较慢的时候就不行, 比如全文搜索.复杂查询也不很适用. )
javafancy: (reply gty的第一个问题)
可以试一下CacheResultSet类.
(点评: jdk1.2.2 版本的 doc 中没有找到这个类. )
yzw: (reply gty的第一个问题)
我是用beans取得数据,每一个页面用一次查询,在把查询结果定位到适当地位置用rs.absolute()方法。
然后取出10条记录,放在vector中,返回到jsp页面上。
jsp上<jsp:usebeans scope="session"...>
然后就返回啦!
缺点: absolute方法是jdbc2.0的方法,在oracle的thin driver中实现方法不是很好,和每次next过去也差不多了,:( 每一个页面用一次查询其实对性能的要求并不是很高的, 特别是你用了connectionPool之后,呵呵,速度很快的说.
(点评:
这是另一种分页的方法, 每次查询. 有些数据库实现了 limit 功能, 可直接取出相应的结果集, 比如 mysql, postgresql 等. 而其他一些, 比如 oracle 等没有这个功能, 只好手动来取了. 数据库连接池在这个方法中非常重要, 一般应该使用, 不然每次都创建连接太慢了. 还可以再优化, 建立起 cache 系统, 只查询出 primary key, 在 cache 中取相应的结果, 可减少时间)
xuyu:
我想请教一下有关connectionPool的问题,我参照例程用HashTable+Vector做了一个pool
但是使用了ConnectionPool反而查询速度下降了?
这是什么原因?
对了,你每次是conn.close()还是connMgr.freeConnection()?
yzw:
我现在在每一个页面中产生一个新的connection, resultset,查询完成后
在close掉.
我想如果在一个session中共享一个全局的resultset, 每次absolute(10)(向后)
或者absolute(-10)(向前),速度一定会快不少.也就是说不用每一页都进行一次
查询,只在第一次时进行一次查询,以后就用rs向前向后滚动.
不过让一个用户就占用一个session的resultset(statement,connection...)
呵呵,系统开销太大.
(点评:
最好不要直接使用 result, 可用 bean 包装起来, 这样就大大简化表现层,
并且符合三层思想.
)
gty: (reply yzw的解答)
太好了,用absolute()代替next()是个好方法,我怎么没想到呢。也许上次被Sun的JDBC ODBC bridge折腾坏了,很长一段时间我不敢用JDBC2.0的新API。
谢谢你啦。
但你说如果加上一些新条件,让数据库把前面没用的数据过滤掉,会不会更好呢?
一个是Java程序来做过滤,一个是Datbase来做过滤,那种好,哪位数据库大牛来回答?
(点评: 其实这个没有好的方法, 比如排序的时候, 没有办法过滤掉, 必须全部查出来才行.)
holly:
用 Database 做过滤效率高, 网络传输量小.
(点评:
这里的传输并不是说往客户端的传输, 实际上往客户端的数据对于特定的一页来说, 数据量是固定的. 很多时候, application server 和 databse server并不是一台服务器, 这时候查询就需要在两台机器之间传输数据, 所以就需要对查询语句作优化, 比如并不查询出一条记录的所有的数据, 只查出 primary key,然后配合 application server 端的 cache 系统, 可以减少数据传输. 总的来说, 尽量让数据库多作一些工作, 毕竟它做的要比你自己实现要快一些.
)
SuperMMX:
String[] getResult(int startIndex, int resultNumber)
{
得到一个全部的结果集, ResultSet rs;
"select * from aTable";
for (int i = 0; i < startIndex; i ++)
{
rs.next();
}
String[] temp = new String[resultNumber];
for (int i = 0; i < resultNumber; i ++)
{
temp[i] = rs.getString("aField");
}
return temp;
}
这个就是自己实现取出相应的数据集, 对于没有 limit 功能的数据库来说,
也是没有办法的办法.
SmilerConquer:
不是可以用absolute定位ResultSet的指针吗?
当初就是拿这个absolute(-1)来做ResultSet的size的
(点评:
这是 jdbc 2.0 的方法, 但很多数据库都没有相应的驱动, 所以也是一种不得已的办法.
)
总结:
这次的讨论基本上提出了分页所存在的一些问题, 以及一些简单的解决方案,更深层次的权衡还待探讨. 这里的解决方案只是原理上的, 具体方法根据需要的不同可自己选择合适的.