Saturday, December 1, 2007

apache+tomcat+mysql 的负载平衡和集群技术.

http://www.chinaunix.net/jh/26/1018155.html
公司开发了一个网站,估计最高在线人数是3万,并发人数最多100人。开发的网站是否能否承受这个压力,如何确保网站的负荷没有问题,经过研究决定如下:
(1) 采用负载平衡和集群技术,初步机构采用Apache+Tomcat的机群技术。
(2) 采用压力测试工具,测试压力。工具是Loadrunner。
硬件环境搭建:
为了能够进行压力测试,需要搭建一个环境。刚开始时,测试在公司局域网内进行,但很快发现了一个问题,即一个脚本的压力测试结果每次都不一样,并且差别很大。原来是受公司网络的影响,于是决定搭建一个完全隔离的局域网测试。搭建后的局域网配置如下:
(1) 网络速度:100M
(2) 三台服务器:
负载服务器 :操作系统windows2003,
Tomcat服务器:操作系统windows2000 Professional
数据库服务器:操作系统windows2000 Professional
三台机器的cpu 2.4 G, 内存 1G。
软件环境搭建:
软件的版本如下:
Apache 版本:2.054,
Tomcat5.0.30,
mysql :4.1.14.
JDK1.5
压力测试工具:Loadrunner7.8。

负载平衡方案如下:
一台机器(操作系统2003)安装apache,作为负载服务器,并安装tomcat作为一个worker;一个单独安装tomcat,作为第二个worker;剩下的一台单独作为数据库服务器。
Apache和tomcat的负载平衡采用JK1.2.14(没有采用2.0,主要是2.0不再维护了)。
集群方案:
采用Tomcat本身的集群方案。在server.xml配置。
压力测试问题:
压力测试后,发现了一些问题,现一一列出来:
(1) 采用Tocmat集群后,速度变得很慢。因为集群后,要进行session复制,导致速度较慢。Tomcatd的复制,目前不支持 application复制。复制的作用,主要用来容错的,即一台机器有故障后,apache可以把请求自动转发到另外一个机器。在容错和速度的考虑上, 我们最终选择速度,去掉了Tomcat集群。
(2) 操作系统最大并发用户的限制:
为了采用网站的压力,我们开始的时候,仅测试Tomcat的最大负载数。Tomcat服务器安装的操作系统是 windows2000 Professional。当我们用压力测试工具,并发测试时,发现只要超过15个并发用户,会经常出现无法连接服务器的情况。 经过研究,发现是操作系统的问题:windows2000 Professional 支持的并发访问用户有限,默认的好像是15个。于是我们把操作系统 全部采用windows2003 server版本。
(3) 数据库连接池的问题:
测试数据库连接性能时,发现数据库连接速度很慢。每增加一些用户,连接性能就差了很多。我们采用的数据库连接池是DBCP,默认的初始化为50 个,应该不会很慢吧。查询数据库的连接数,发现初始化,只初始化一个连接。并发增加一个用户时,程序就会重新创建一个连接,导致连接很慢。原因就在这里 了。如何解决呢?偶尔在JDK1.4下的Tomcat5.0.30下执行数据库连接压力测试,发现速度很快,程序创建数据库连接的速度也是很快的。看来 JDK1.5的JDBC驱动程序有问题。于是我们修改 JDK的版本为1.4.

(4) C3P0和DBCP
C3P0是Hibernate3.0默认的自带数据库连接池,DBCP是Apache开发的数据库连接池。我们对这两种连接池进行压力测试对比,发现在并发300个用户以下时,DBCP比C3P0平均时间快1秒左右。但在并发400个用户时,两者差不多。

速度上虽然DBCP比C3P0快些,但是有BUG:当DBCP建立的数据库连接,因为某种原因断掉后,DBCP将不会再重新创建新的连接,导致必须重新启动Tomcat才能解决问题。DBCP的BUG使我们决定采用C3P0作为数据库连接池。
调整后的方案:
操作系统Windows2003 server版本
JDK1.4
Tomcat 5.0.30
数据库连接池C3P0
仅采用负载平衡,不采用集群。
软件的配置:
Apache配置:主要配置httpd.conf和新增加的文件workers.properties
Httpd.conf:
#一个连接的最大请求数量
MaxKeepAliveRequests 10000
#NT环境,只能配置这个参数来提供性能

#每个进程的线程数,最大1920。NT只启动父子两个进程,不能设置启动多个进程
ThreadsPerChild 1900
每个子进程能够处理的最大请求数
MaxRequestsPerChild 10000


# 加载mod_jk
#
LoadModule jk_module modules/mod_jk.so
#
# 配置mod_jk
#
JkWorkersFile conf/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel info
#请求分发,对jsp文件,.do等动态请求交由tomcat处理
DocumentRoot "C:/Apache/htdocs"
JkMount /*.jsp loadbalancer
JkMount /*.do loadbalancer
JkMount /servlet/* loadbalancer
#关掉主机Lookup,如果为on,很影响性能,可以有10多秒钟的延迟。
HostnameLookups Off
#缓存配置
LoadModule cache_module modules/mod_cache.so
LoadModule disk_cache_module modules/mod_disk_cache.so
LoadModule mem_cache_module modules/mod_mem_cache.so


CacheForceCompletion 100
CacheDefaultExpire 3600
CacheMaxExpire 86400
CacheLastModifiedFactor 0.1


CacheEnable disk /
CacheRoot c:/cacheroot
CacheSize 327680
CacheDirLength 4
CacheDirLevels 5
CacheGcInterval 4


CacheEnable mem /
MCacheSize 8192
MCacheMaxObjectCount 10000
MCacheMinObjectSize 1
MCacheMaxObjectSize 51200


worker. Properties文件
#
# workers.properties ,可以参考
http://jakarta.apache.org/tomcat/connectors-doc/config/workers.html
# In Unix, we use forward slashes:
ps=

# list the workers by name

worker.list=tomcat1, tomcat2, loadbalancer

# ------------------------
# First tomcat server
# ------------------------
worker.tomcat1.port=8009
worker.tomcat1.host=localhost
worker.tomcat1.type=ajp13

# Specify the size of the open connection cache.
#worker.tomcat1.cachesize

#
# Specifies the load balance factor when used with
# a load balancing worker.
# Note:
# ----> lbfactor must be > 0
# ----> Low lbfactor means less work done by the worker.
worker.tomcat1.lbfactor=900

# ------------------------
# Second tomcat server
# ------------------------
worker.tomcat1.port=8009
worker.tomcat1.host=202.88.8.101
worker.tomcat1.type=ajp13

# Specify the size of the open connection cache.
#worker.tomcat1.cachesize

#
# Specifies the load balance factor when used with
# a load balancing worker.
# Note:
# ----> lbfactor must be > 0
# ----> Low lbfactor means less work done by the worker.
worker.tomcat1.lbfactor=2000

# ------------------------
# Load Balancer worker
# ------------------------

#
# The loadbalancer (type lb) worker performs weighted round-robin
# load balancing with sticky sessions.
# Note:
# ----> If a worker dies, the load balancer will check its state
# once in a while. Until then all work is redirected to peer
# worker.
worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=tomcat1,tomcat2

#
# END workers.properties
#

Tomcat1配置:

port="8080" maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true" />


maxThreads="500" minSpareThreads="400" maxSpareThreads="450"
enableLookups="false" redirectPort="8443" debug="0"
protocol="AJP/1.3" />



启动内存配置,开发configure tomcat程序即可配置:
Initial memory pool: 200 M
Maxinum memory pool:300M
Tomcat2配置:
配置和tomcat1差不多,需要改动的地方如下:



启动内存配置,开发configure tomcat程序即可配置:
Initial memory pool: 512 M
Maxinum memory pool:768M
Mysql配置:
Server类型:Dedicated MySQL Server Machine
Database usage:Transational Database Only
并发连接数量:Online Transaction Processing(OLTP)
字符集:UTF8
数据库连接池的配置:
我们采用的是spring 框架,配置如下:


org.hibernate.dialect.MySQLDialect
com.mysql.jdbc.Driver
jdbc:mysql://202.88.1.103/db
sa


false
false

true
2

200
5
12000
50
1

Monday, November 12, 2007

Why Dojo 1.0 Matters - Ajax Now Enterprise-Ready

http://www.keeneview.com/labels/Silverlight.html
You can't swing a dead cat at a Web 2.0 conference these days without hitting a dozen Rich Internet Application (RIA) toolkits. The Olliance Group recently identified 58 RIA products, many of them either proprietary or incompatible with the other 57 approaches.

Just to set the stage, here is my personal definition for a Rich Internet Application:

"Rich Internet Applications match the responsiveness of traditional desktop apps by minimizing web page refreshes. RIA taps into the collective power of the Internet to supply widgets and services for building web clients, like rss feeds, Google maps and Youtube. The goal of RIA is not merely to emulate a PC GUI in a browser (aka the Silverlight sell-out), but to deliver browser-based clients which far outperform PC GUIs in speed and functionality."

Ajax* is a particular architecture for building RIAs that is favored by open source libraries such as Dojo, Jquery, Ext and dozens of others. The perpetual flamewars between adherents of the various Ajax toolkits is a huge gift for the proprietary RIA products like Microsoft Silverlight and Adobe Flex.

Many Ajax toolkits seem more focused on posting esoteric animated graphics widgets to the Ajaxian web site than they are on meeting mundane but critical enterprise needs. To be fair, whizzy graphics are an important element of RIA's appeal. However there are more fundamental concerns to address before RIA can be considered enterprise-ready.

Into this maelstrom of splintered efforts comes the Dojo 1.0 release. Dojo has been in development for 3 years, making it one of the more mature toolkits available. Dojo also has the backing of IBM, BEA and Sun and will ship standard with their Java servers.

Previous versions of Dojo were criticized as being too big and too slow. Dojo 1.0 attempts to address those issues. Even more importantly, Dojo has also addressed a number of the hard issues required to gain enterprise adoption:
  1. Internationalization and accessibility: Dojo supports localization, keyboard navigation and vision-impaired users, making it appropriate for both global businesses and government applications.

  2. Excellent data handling: the Dojo grid (plug: built by ActiveGrid's own Scott Miles and Steve Orvell) easily handles 100,000+ rows with dynamic loading and complex rows, making it suitable for building the most data-intensive web clients.

  3. Interoperability: Dojo supports the OpenAjax hub, making it possible for an enterprise to integrate and support web applications built with different Ajax toolkits.

  4. Corporate look and feel: Dojo supports the ability to define a corporate look and feel or skin that can be used across a set of applications.
Although choice in general is good, no CIO wants to be stuck supporting a dozen squabbling Ajax toolkits a year from now. Dojo 1.0 may not be the ultimate winner, but it sets clear expectations for what an enterprise-ready Ajax toolkit should be able to do.

Saturday, October 6, 2007

JSF & Portlet

http://wiki.apache.org/myfaces/CreatingJSFPortlets

Option 1: MyFacesGenericPortlet

  1. Make sure your JSF MyFaces application runs as a stand-alone servlet.

  2. Remove any redirects from your faces-config.xml. Portlets can not handle these.

  3. Create a Portlet WAR as per the instructions for your Portlet container. Make sure it contains everything that was included in step 1.

  4. Update your portlet.xml file as follows:


org.apache.myfaces.portlet.MyFacesGenericPortlet





default-view
/some_view_id_from_faces-config








default-view-selector
com.foo.MyViewSelector

DWR 简化 Ajax 的 portlet 间通信

http://blogger.org.cn/blog/more.asp?name=lhwork&id=18396

许多开发人员都期待着利用 Ajax 技术来提高基于 Web 的应用程序的用户体验,但是 Ajax 编程可能是一项麻烦的任务。开放源码的 Direct Web Remoting (DWR) 库通过自动把 Java 类转换成 JavaScript 类,可以为 Java™ 开发人员简化 Ajax 开发。在这篇文章中,将学习如何用 DWR 和符合 JSR-168 规范的 portlet 迅速而容易地构建 Ajax 应用程序。

Portlet 是基于 Java 平台的 Web 门户应用程序。JSR-168 是开发 portlet 应用程序的 Java Community Process 标准,它描述了 portlet 生命周期管理、portlet 容器合约、打包、部署以及与门户有关的其他方面。

异步 JavaScript + XML(或者叫做 Ajax)是一项用于开发丰富、交互的 Web 应用程序的技术。Ajax 组合了 XML、HTML、DHTML、JavaScript 和 DOM。

Portlet 和 Ajax 看起来彼此之间是完美搭配,因为它们都侧重于用 Web 浏览器作为向用户呈现用户界面的工具。把这两者与 Java 技术组合在一起的简易方式就是使用 DWR 库。DWR 是 Apache 许可下的开放源码 Java 库,用于构建基于 Ajax 的 Web 应用程序。DWR 的基本目的是向开发人员隐藏 Ajax 的细节。您在服务器端使用普通 Java 对象(POJO),而 DWR 动态地生成 JavaScript 代理函数,所以使用 JavaScript 的客户端开发感觉起来就像直接调用 JavaBean。DWR 的主要组件是一个 Java servlet,处理从浏览器到服务器的调用。

本文使用 DWR、基于三个 portlet 来构建一个示例 Ajax 应用程序。我将介绍如何把 DWR 与 porlet 应用程序集成,但是我不想深入 DWR 的幕后工作细节;在这个项目的 Web 站点和 developerWorks 的页面上(请参阅 参考资料 获得细节)可以找到关于这个库的更多信息。要构建我描述的应用程序,需要 1.3 或以后版本的 Java 平台和符合 JSR-168 规范的的门户环境。我用来开发和测试这个代码的环境包含 IBM Rational Application Developer V6.0、Apache Jetspeed 2.0 portal 和 Java 5.0。

Monday, September 24, 2007

Sysdeo Tomcat PlugIn

首先,讲讲在Eclipse中如何使用Tomcat插件.安装好Tomcat插件后,启动Eclipse.如果已成功安装了Tomcat插件后,打开Window->Preferences,在弹出的窗口点击Tomcat,进行Tomcat的相关置:

我使用的是Tomcat5 ,插件的版本与Eclipse的版本是否匹配.在开发时,一定要注意 Eclipse的版本与对应的插件版本是否支持。否则造成意外无法启动。我在开发中有一次用Eclipse3.1使Tomcat3插件,出现很多意想不到 的原因,也找不到原因。最后更换为Tomcat3.1问题解决。可见插件的版本是否被Eclipse支持十重要要。

1.tomcat version (选择相应的Tomcat版本)

2.tomcat home (通过Browse选择在本地安装了的Tomcat的路径)

3.Context declaration Mode(选择Context声明的类型,1.如果选择server.xml,则新建Tomcat工程后,会自动在Server.xml文件中添加 Context属性.2.如果选择Context files,则会单独新建一个xml文件,这个文件中只有Context属性,并且自动以工程的名字命名这个文件)

1.Server.xml 将Context属性加入到 %TomcatHome%\conf\server.xml文件中
2.Context files 单独以工程名字建立一个XML文件,project.xml放置在%TomcatHome%\conf\\Catalina\localhost目录下文件只有
Context属性.

3.部署Web应用
1.拷贝war文件或Web应用文件夹至%Tomcat_Home%/server/webapps/目录下
2.为Web服务建立一个只包括Context内容的XML文件放置在%Tomcat_Home%/server/webapps/目录下,这时Web应用可以放置在硬盘的任何地方


4.Tomcat限制特定主机访问

allow="127.0.0.1"
deny="" />

5.Eclipse中新建一个Tomcat工程,会自动根据你在Eclipse配置Tomcat的属性

1,在%Tomcat_Home%/conf/server.xml中增加属性%

或者

2.Tomcat_Home%/conf/Catalina/localhost目录下新建一个以工程名字的XMl文件且其中只有属性。

若在Eclipse中删除了工程,再次启动Tomcat时,有时候会报错说找不到工程文件.原因可 能是虽然已经删除了工程,但是却有可能没有在Tomcat配置文件删除.比如:Tomcat_Home% /conf/Catalina/localhost目录下的文件没有被删除.删除即可.

Friday, September 7, 2007

“Don’t Waste Time” with Graphical Ajax Solutions

http://ajaxian.com/archives/dont-waste-time-with-graphical-ajax-solutions#comment-251827

ZDNet’s Ryan Stewart argues against performing interactive graphics with Ajax (i.e. standard web technologies). The article relates to my post on different techniques for graphics with Ajax (covered on Ajaxian).

Michael responds that the main argument against Flash is the user base, then goes on to list the plusses and minuses of "richer plugins".

You simply should not be trying to create a rich, graphical experience in Ajax. The options (SVG, Canvas, VML, ect) are buggy, supported in different ways depending on the browser, and, for the most part, are a poor experience for both users and developers.

The kind of rich interactivity that Flash and Windows Presentation Foundation provide are going to be leaps and bounds ahead of what any browser technology can do, and that's why they will succeed. The web becomes richer every day. Video and Music are taking the web by storm, and with the surge in broadband adoption, people are making these things part of their every day web experience. Ajax applications can't take advantage of them in the way the Flash or WPF can.

Flash (more generally, Richer Plugins) was actualy one of the graphics techniques mentioned in the original article. It’s all about trade-offs. I’ve actually argued myself that Ajax developers ought to take Flash more seriously, as it’s an excellent complement to Ajax. Flash sometimes makes a nice sweet spot - with graphics and multimedia closer to that of the desktop than standard DHTML/Ajax, but still living in a web platform that’s often more convenient than the desktop. The two monster apps of the past 12-18 months, YouTube and MySpace, demonstrate the power of Flash and multimedia on the web.

The benefits of Flash over Ajax are self-evident and undeniable, but Flash comes with its own set of problems too - not every user has Flash installed, not every user has the latest version, not every network allows Flash applications to run, not every developer and company wants to commit to proprietary technology when viable alternatives are available. Ajax apps tend to be easier to degrade gracefully as well; Flash is more all-or-none.

What if I want to introduce a histogram to an Ajax enterprise app? I’ll happily use a DOM/CSS library like CSS Graphs. Or if I’m writing a Firefox extension, I might use a data: resource to create a whimpy graphic since I no longer care about portability or even extravagant display. Maybe I want a 16×16 heatmap next to each search result - I’ll draw it with a Canvas and keep all the search results in standard HTML. And so on. See? Competent developers don’t engage in dogmatic battles, because they know software is all about trade-offs. Many times, Flash wins. Many times, it loses.

Last word goes to Ryan:

Don’t waste time trying to build the next generation of the web with graphical Ajax solutions … you already have a solution, and it’s getting more robust by the day. As your web applications start to require a more rich environment, embrace Rich Internet Applications - you’ll be better off.

Thursday, September 6, 2007

a SVG whiteboard example from Mark Finkle

http://starkravingfinkle.org/blog/2006/04/richdraw-simple-vmlsvg-editor/

RichDraw works in IE 6+, Firefox 1.5+ and Opera 9, using VML or SVG as the underlying renderer. Opera 9 has a small issue with the way I adjust/offset the mouse coordinates, but I’ll look into that. Currently the component supports:

  • Creating basic shapes (rectangle, rounded rectangle and ellipse) and lines.
  • Selecting shapes.
  • Deleting selected shapes.
  • Dragging shapes with mouse.
  • Setting fill color.
  • Setting line color and width.
  • Retrieving the markup.

I don’t know if there are any real uses for RichDraw in it’s current state. There is a lot that could be done to enhance RichDraw and make it more usable. Probably the most important feature would be loading markup back into the editor. On the surface, that’s easy enough to add. However, there could be situations where the markup is saved from IE (VML) and loaded back into Firefox (SVG). This would not work. I am planning on converting the VML to SVG when retrieving and converting it back when loading. That way RichDraw always appears to be using SVG. I will build from my IESVG code to handle the conversions.

Other enhancements include:

  • Resizing selected shapes.
  • Ordering (Bring to Front, Send to Back).
  • Scrollable workarea.
  • Inserting text.
  • Inserting images.

Required files: richdraw.js, svgrenderer.js, vmlrenderer.js

Demo file: richdraw_demo.htm

http://cristian.nexcess.net/ajax/whiteboard/


Sunday, August 26, 2007

js -- grab/drag/drop

var mousex = 0;
var mousey = 0;
var grabx = 0;
var graby = 0;
var orix = 0;
var oriy = 0;
var elex = 0;
var eley = 0;
var algor = 0;

var dragobj = null;

function falsefunc() { return false; } // used to block cascading events

function init()
{
document.onmousemove = update; // update(event) implied on NS, update(null) implied on IE
update();
}

function getMouseXY(e) // works on IE6,FF,Moz,Opera7
{
if (!e) e = window.event; // works on IE, but not NS (we rely on NS passing us the event)

if (e)
{
if (e.pageX || e.pageY)
{ // this doesn't work on IE6!! (works on FF,Moz,Opera7)
mousex = e.pageX;
mousey = e.pageY;
algor = '[e.pageX]';
if (e.clientX || e.clientY) algor += ' [e.clientX] '
}
else if (e.clientX || e.clientY)
{ // works on IE6,FF,Moz,Opera7
mousex = e.clientX + document.body.scrollLeft;
mousey = e.clientY + document.body.scrollTop;
algor = '[e.clientX]';
if (e.pageX || e.pageY) algor += ' [e.pageX] '
}
}
}

function update(e)
{
getMouseXY(e); // NS is passing (event), while IE is passing (null)
}

function grab(context)
{
document.onmousedown = falsefunc; // in NS this prevents cascading of events, thus disabling text selection
dragobj = context;
dragobj.style.zIndex = 10; // move it to the top
document.onmousemove = drag;
document.onmouseup = drop;
grabx = mousex;
graby = mousey;
elex = orix = dragobj.offsetLeft;
eley = oriy = dragobj.offsetTop;
update();
}

function drag(e) // parameter passing is important for NS family
{
if (dragobj)
{
elex = orix + (mousex-grabx);
eley = oriy + (mousey-graby);
dragobj.style.position = "absolute";
dragobj.style.left = (elex).toString(10) + 'px';
dragobj.style.top = (eley).toString(10) + 'px';
}
update(e);
return false; // in IE this prevents cascading of events, thus text selection is disabled
}

function drop()
{
if (dragobj)
{
dragobj.style.zIndex = 0;
dragobj = null;
}
update();
document.onmousemove = update;
document.onmouseup = null;
document.onmousedown = null; // re-enables text selection on NS
}

Monday, August 20, 2007

Tomcat 的数据库连接池设置与应用

1.将数据库驱动程序的JAR文件放在Tomcat的 common/lib 中;

2.在server.xml中设置数据源,以MySQL数据库为例,如下:
在<globalnamingresources> <globalnamingresources>节点中加入,
 <Resource
name="jdbc/DBPool"
type="javax.sql.DataSource"
password="root"
driverClassName="com.mysql.jdbc.Driver"
maxIdle="2"
maxWait="5000"
username="root"
url="jdbc:mysql://127.0.0.1:3306/test"
maxActive="4"/>
属性说明:name,数据源名称,通常取”jdbc/XXX”的格式;
type,”javax.sql.DataSource”;
password,数据库用户密码;
driveClassName,数据库驱动;
maxIdle,最大空闲数,数据库连接的最大空闲时间。超过空闲时间,数据库连
接将被标记为不可用,然后被释放。设为0表示无限制。
MaxActive,连接池的最大数据库连接数。设为0表示无限制。
maxWait ,最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示
无限制。

3.在你的web应用程序的web.xml(WEB-INF/web.xml)中设置数据源参考,如下:
节点中加入,

MySQL DB Connection Pool
jdbc/DBPool
javax.sql.DataSource
Container
Shareable

子节点说明: description,描述信息;
res-ref-name,参考数据源名字,同上一步的属性name;
res-type,资源类型,”javax.sql.DataSource”;
res-auth,”Container”;
res-sharing-scope,”Shareable”;

4.在web应用程序的context.xml (META-INF)中设置数据源链接,如下:
节点中加入,

属性说明:name,同第2步和第3步的属性name值,和子节点res-ref-name值;
type,同样取”javax.sql.DataSource”;
global,同name值。

至此,设置完成,下面是如何使用数据库连接池。
1.建立一个连接池类,DBPool.java,用来创建连接池,代码如下:
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DBPool {
private static DataSource pool;
static {
Context env = null;
try {
env = (Context) new InitialContext().lookup("java:comp/env");
pool = (DataSource)env.lookup("jdbc/DBPool");
if(pool==null)
System.err.println("'DBPool' is an unknown DataSource");
} catch(NamingException ne) {
ne.printStackTrace();
}
}
public static DataSource getPool() {
return pool;
}
}

2. 在要用到数据库操作的类或jsp页面中,用DBPool.getPool().getConnection(),获得一个Connection对象,就可 以进行数据库操作,最后别忘了对Connection对象调用close()方法,注意:这里不会关闭这个Connection,而是将这个 Connection放回数据库连接池。

Monday, August 13, 2007

TD Maximum Colspan

Anyway to get around IEs rendering of a TD with a colspan of more than
1000? (right now it ignores 1000 and displays the TD as a cell with no
colspan)

Maybe it's a really, really large multiplication table. Or a really,
really big linear programming matrix.

Or, maybe he/she/it is trying to achieve pixel-perfect layout up to
(pulling number out of hat) 1024 pixels wide.

I'd be impressed to see a browser render that without choking a bit.

http://www.intraproducts.com/beta/usenet/1101cells.asp

Nooooo, thats not the spec. So, in the end i'm guessing the answer is
NO, IE does not support colspans of > 1000.

Sunday, August 12, 2007

Modify Classpath At Runtime

I've seen a lot of forum posts about how to modify the classpath at runtime and a lot of answers saying it can't be done. I needed to add JDBC driver JARs at runtime so I figured out the following method.

The system classloader (ClassLoader.getSystemClassLoader()) is a subclass of URLClassLoader. It can therefore be casted into a URLClassLoader and used as one.

URLClassLoader has a protected method addURL(URL url), which you can use to add files, jars, web addresses - any valid URL in fact.

Since the method is protected you need to use reflection to invoke it.

Here's some code for a class which adds a File or URL to the classpath:

import java.lang.reflect.*;
import java.io.*;
import java.net.*;
public class ClassPathHacker {

private static final Class[] parameters = new Class[]{URL.class};

public static void addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}//end method

public static void addFile(File f) throws IOException {
addURL(f.toURL());
}//end method


public static void addURL(URL u) throws IOException {

URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;

try {
Method method = sysclass.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(sysloader,new Object[]{ u });
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}//end try catch

}//end method

}//end class

Wednesday, August 8, 2007

jdbc handling of blob type

Blob

A Blob is a JDBC interface mapping for an SQL BLOB. A Blob is obtained by the getBlob() methods of a ResultSet or CallableStatement. A Blob has methods to get its number of bytes and to determine the starting position of another Blob or an array of bytes in the current Blob. These methods work without materializing the data. To materialize the data, you can use getBinaryStream() or getBytes() ( for part or all of the Blob ) and then construct usable objects from the returned stream or byte array.

For Blob storage use setBlob() from a PreparedStatement or updateObject from an updatable ResultSet. Again, this is where most discussions end, with the example retrieving a Blob from one row and putting it to another row in the same or a different table.

How does Blob data get there in the first place? Again, look at the Blob methods, this time getBinaryStream() and getBytes(): use PreparedStatement's setBinaryStream() or setBytes() methods to populate the Blob.

Html online editor

看了现在网上流行的在线编辑器,也忍不住想了解一下原理。下了目前应用最广泛的eWebEdit,这个是我见到的最强的开源在线编辑器...研究了一天,终于知道了核心原理。

先解释一下在线编辑器的原理: 首先需要IE5.0以上版本的支持。因为IE5.0以上版本有一个编辑状态,可以在一个iframe里面输入文字。然后通过 "document.body.innerHTML"可以获取iframe里面的html代码,这个就是关键。那怎么才能让ifrmae处于编辑状态呢, 可以用:

function document.onreadystatechange()
{
HtmlEdit.document.designMode="On";
}

函数实现。剩下的问题就是就是取得焦点和选中的值:

HtmlEdit.focus();
var sel = HtmlEdit.document.selection.createRange();

以上2句可以获取选中的值的html代码。

到 了这里,基本原理搞清楚了,然后我们可以用 insertHTML("str")方法将html字符替换掉选种的值。以下就给出一个简单的demo来演示只有加粗效果的在线编辑器。我这里用了一个 textarea来或得iframe里的html值,实际情况,可以将textarea的display设置成false,然后就可以将iframe的内 容提交.
函数实现。剩下的问题就是就是取得焦点和选中的值:

HtmlEdit.focus();
var sel = HtmlEdit.document.selection.createRange();

以上2句可以获取选中的值的html代码。

到 了这里,基本原理搞清楚了,然后我们可以用 insertHTML("str")方法将html字符替换掉选种的值。以下就给出一个简单的demo来演示只有加粗效果的在线编辑器。我这里用了一个 textarea来或得iframe里的html值,实际情况,可以将textarea的display设置成false,然后就可以将iframe的内 容提交.
函数实现。剩下的问题就是就是取得焦点和选中的值:

HtmlEdit.focus();
var sel = HtmlEdit.document.selection.createRange();

以上2句可以获取选中的值的html代码。

到 了这里,基本原理搞清楚了,然后我们可以用 insertHTML("str")方法将html字符替换掉选种的值。以下就给出一个简单的demo来演示只有加粗效果的在线编辑器。我这里用了一个 textarea来或得iframe里的html值,实际情况,可以将textarea的display设置成false,然后就可以将iframe的内 容提交.

http://www.cnbruce.com/blog/showlog.asp?log_id=1168

Monday, August 6, 2007

recommended JDBC coding pattern

Note that connections, statements, and resultsets often tie up operating system resources such as sockets or file descriptors. In the case of connections to remote database servers, further resources are tied up on the server, eg. cursors for currently open resultsets. It is vital to close() any JDBC object as soon as it has played its part; garbage collection should not be relied upon. Forgetting to close() things properly results in spurious errors and misbehaviour. The above try-finally construct is a recommended code pattern to use with JDBC objects.

Statement stmt = conn.createStatement();
try {
stmt.executeUpdate( "INSERT INTO MyTable( name ) VALUES ( 'my name' ) " );
} finally {
//It's important to close the statement when you are done with it
stmt.close();
}
Data is retrieved from the database using a database query mechanism. The example below shows creating a statement and executing a query.
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery( "SELECT * FROM MyTable" );
try {
while ( rs.next() ) {
int numColumns = rs.getMetaData().getColumnCount();
for ( int i = 1 ; i <= numColumns ; i++ ) {
//Column numbers start at 1.
//Also there are many methods on the result set to return
// the column as a particular type. Refer to the Sun documentation
// for the list of valid conversions.
System.out.println( "COLUMN " + i + " = " + rs.getObject(i) );
}
}
} finally {
rs.close();
}
} finally {
stmt.close();
}

Connection Pooling

Connection pooling with MySQL Connector/J

http://dev.mysql.com/tech-resources/articles/connection_pooling_with_connectorj.html

Connection pooling is a technique of creating and managing a pool of connections that are ready for use by any thread that needs them.

When the connection is "loaned out" from the pool, it is used exclusively by the thread that requested it. From a programming point of view, it is the same as if your thread called DriverManager.getConnection() every time it needed a JDBC connection, however with connection pooling, your thread may end up using either a new, or already-existing connection.

Luckily, Sun has standardized the concept of connection pooling in JDBC through the JDBC-2.0 "Optional" interfaces, and all major application servers have implementations of these APIs that work fine with MySQL Connector/J.

Generally, you configure a connection pool in your application server configuration files, and access it via the Java Naming and Directory Interface (JNDI).

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import javax.naming.InitialContext;
import javax.sql.DataSource;


public class MyServletJspOrEjb {

public void doSomething() throws Exception {
/*
* Create a JNDI Initial context to be able to
* lookup the DataSource
*
* In production-level code, this should be cached as
* an instance or static variable, as it can
* be quite expensive to create a JNDI context.
*
* Note: This code only works when you are using servlets
* or EJBs in a J2EE application server. If you are
* using connection pooling in standalone Java code, you
* will have to create/configure datasources using whatever
* mechanisms your particular connection pooling library
* provides.
*/

InitialContext ctx = new InitialContext();

/*
* Lookup the DataSource, which will be backed by a pool
* that the application server provides. DataSource instances
* are also a good candidate for caching as an instance
* variable, as JNDI lookups can be expensive as well.
*/

DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/MySQLDB");

/*
* The following code is what would actually be in your
* Servlet, JSP or EJB 'service' method...where you need
* to work with a JDBC connection.
*/

Connection conn = null;
Statement stmt = null;

try {
conn = ds.getConnection();

/*
* Now, use normal JDBC programming to work with
* MySQL, making sure to close each resource when you're
* finished with it, which allows the connection pool
* resources to be recovered as quickly as possible
*/

stmt = conn.createStatement();
stmt.execute("SOME SQL QUERY");

stmt.close();
stmt = null;

conn.close();
conn = null;
} finally {
/*
* close any jdbc instances here that weren't
* explicitly closed during normal code path, so
* that we don't 'leak' resources...
*/

if (stmt != null) {
try {
stmt.close();
} catch (sqlexception sqlex) {
// ignore -- as we can't do anything about it here
}

stmt = null;
}

if (conn != null) {
try {
conn.close();
} catch (sqlexception sqlex) {
// ignore -- as we can't do anything about it here
}

conn = null;
}
}
}
}
The most important thing to remember when using connection pooling is to make sure that no matter what happens in your code (exceptions, flow-of-control, etc), connections, and anything created by them (statements, result sets, etc) are closed, so that they may be re-used, otherwise they will be "stranded," which in the best case means that the MySQL server resources they represent (buffers, locks, sockets, etc) may be tied up for some time, or worst case, may be tied up forever.

Connection Pooling Configuration Documentation


Place a copy of mysql-connector-java-[version]-bin.jar in $CATALINA_HOME/common/lib/. Then, follow the instructions in the section MySQL DBCP Example of the Tomcat documentation.


Notice that Connector/J 3.0 and newer work with the same settings: http://jakarta.apache.org/tomcat/tomcat-4.1-doc/jndi-datasource-examples-howto.html


long time idle mysql connection

I have a servlet/application that works fine for a day, and then stops working overnight

MySQL closes connections after 8 hours of inactivity. You either need to use a connection pool that handles stale connections or use the "autoReconnect" parameter.

Also, you should be catching SQLExceptions in your application and dealing with them, rather than propagating them all the way until your application exits, this is just good programming practice. MySQL Connector/J will set the SQLState (see java.sql.SQLException.getSQLState() in your APIDOCS) to "08S01" when it encounters network-connectivity issues during the processing of a query. Your application code should then attempt to re-connect to MySQL at this point.

The following (simplistic) example shows what code that can handle these exceptions might look like:


public void doBusinessOp() throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;

// How many times do you want to retry the transaction
// (or at least _getting_ a connection)?

int retryCount = 5;
boolean transactionCompleted = false;

do {
try {
conn = getConnection(); // assume getting this from a
// javax.sql.DataSource, or the
// java.sql.DriverManager

conn.setAutoCommit(false);

//
// Okay, at this point, the 'retry-ability' of the
// transaction really depends on your application logic,
// whether or not you're using autocommit (in this case
// not), and whether you're using transacational storage
// engines
//
// For this example, we'll assume that it's _not_ safe
// to retry the entire transaction, so we set retry
// count to 0 at this point
//
// If you were using exclusively transaction-safe tables,
// or your application could recover from a connection going
// bad in the middle of an operation, then you would not
// touch 'retryCount' here, and just let the loop repeat
// until retryCount == 0.
//
retryCount = 0;

stmt = conn.createStatement();
String query = "SELECT foo FROM bar ORDER BY baz";
rs = stmt.executeQuery(query);
while (rs.next()) {
}
rs.close();
rs = null;
stmt.close();
stmt = null;

conn.commit();
conn.close();
conn = null;

transactionCompleted = true;
} catch (SQLException sqlEx) {

//
// The two SQL states that are 'retry-able' are 08S01
// for a communications error, and 40001 for deadlock.
//
// Only retry if the error was due to a stale connection,
// communications problem or deadlock
//

String sqlState = sqlEx.getSQLState();

if ("08S01".equals(sqlState) || "40001".equals(sqlState)) {
retryCount--;
} else {
retryCount = 0;
}
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException sqlEx) {
// You'd probably want to log this . . .
}
}

if (stmt != null) {
try {
stmt.close();
} catch (SQLException sqlEx) {
// You'd probably want to log this as well . . .
}
}

if (conn != null) {
try {
//
// If we got here, and conn is not null, the
// transaction should be rolled back, as not
// all work has been done

try {
conn.rollback();
} finally {
conn.close();
}
} catch (SQLException sqlEx) {
//
// If we got an exception here, something
// pretty serious is going on, so we better
// pass it up the stack, rather than just
// logging it. . .

throw sqlEx;
}
}
}
} while (!transactionCompleted && (retryCount > 0));
}




There really is no way to keep a connection alive indefitely, and I'm
not aware of compelling reasons to do so for many reasons. First, it
consumes resources that aren't needed if the connection is sitting idle,
and second, it increases the risk that network issues, or server
restarts will cause your application to crash.

It takes very little time (on the order of a few milliseconds) to create
a JDBC connection to MySQL, so you should set your connection pool to
only let connections stay idle for a few minutes.

Most of the high-volume applications I've seen that use Java with MySQL
don't let connections stay idle more than 10 minutes or so.

------------------------------------------------------------------------------
Mark,

Since years you recommend not to just use "autoReconnect=true" but to be
smarter. First hints are in


http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-configuration-p
roperties.html and
http://dev.mysql.com/doc/refman/5.0/en/connector-j-usagenotes-j2ee.html

A few questions:

1) you recommend to "deal with SQLExceptions", do you have any code-examples
how you recommend to do this - a while loop until I finally get a working
connection or a different exception?
In my setup, I use both ssl protected remote jdbc's and regular local jdbc's
and AFAICR, the exceptions I get are not necessarily identical?
You suggest this also http://lists.mysql.com/java/8119 - what would be a
generic approach to do this? I guess if I deal with the exceptions,
testOnBorrow=true no longer makes sense?

2) How do you rate the "testWhileIdle" strategy? If I do that, shouldn't I
add also "minIdle" to ensure that if an idle test fails, a new connection is
added to the pool? If this is configured nicely, do you still think it is
worthwhile to set testOnBorrow=true as long as my application doesn't have a
5*9 SLA?

Also, I use the Jakarta commons-dbcp with BasicDataSource and see that in
org.apache.commons.dbcp.datasources.InstanceKeyDataSource the
"testWhileIdle" keyword is dealt with. But who really does the testing? Is
that connector-j or dbcp?

3) The documentation also lists autoReconnectForPools. How does this fit
into the picture if I have tomcat with dbcp and JNDI resources?
http://lists.mysql.com/java/7774

Many thanks for any hints in advance!

Ralf

Friday, August 3, 2007

用JSP实现图形验证码

用JSP实现图形验证码


import java.io.*;
import java.util.*;
import com.sun.image.codec.jpeg.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.awt.*;
import java.awt.image.*;


public class ValidateCode extends HttpServlet {

private Font mFont=new Font("宋体", Font.PLAIN,12);//设置字体
//处理post
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {

doGet(request,response);
}
public void doGet(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
//取得一个1000-9999的随机数
String s="";

int intCount=0;

intCount=(new Random()).nextInt(9999);//

if(intCount<1000)intcount+=1000;>

s=intCount+"";


//保存入session,用于与用户的输入进行比较.
//注意比较完之后清除session.

HttpSession session=request.getSession (true);

session.setAttribute("validateCode",s);

response.setContentType("image/gif");

ServletOutputStream out=response.getOutputStream();

BufferedImage image=new BufferedImage(35,14,BufferedImage.TYPE_INT_RGB);

Graphics gra=image.getGraphics();
//设置背景色
gra.setColor(Color.yellow);

gra.fillRect(1,1,33,12);
//设置字体色
gra.setColor(Color.black);

gra.setFont(mFont);
//输出数字
char c;

for(int i=0;i<4;i++)>

c=s.charAt(i);

gra.drawString(c+"",i*7+4,11); //7为宽度,11为上下高度位置

}

JPEGImageEncoder encoder=JPEGCodec.createJPEGEncoder(out);

encoder.encode(image);

out.close();

}
}

Use AWT Library in the tomcat or other containers

Use AWT Library in the tomcat or other containers

java.lang.InternalError
: Can't connect to X11 window server
java.lang.InternalError: Can't connect to window server - not enough permissions.

java的图片处理包需要图形环境,而linux上没有启动图形环境,找不到图形环境的server(X11 window server using ':0.0' )所以会报这个错。而通过java -Djava.awt.headless=true 这个参数的指定就可以避免java 2d去找图形环境。

  要么这样试试,应该也可以。在servlet里一开始写一句:

System.setProperty("java.awt.headless","true");

  web服务器的java虚拟机必须加以个参数java.awt.headless=true

  以tomcat为例

  可以在/etc/profile或启动web服务的用户的.bash_profile中的CATALINA_OPTS变量中加入:

CATALINA_OPTS="... -Djava.awt.headless=true"

Sunday, July 22, 2007

How can i get the h:selectOneRadio value with javascript

Hi all.
I have this code
""
<h:selectoneradio id="color" style="font-family: Arial; font-weight: lighter; font-size: 12px;" onclick="showColor()">
<f:selectitem itemlabel="RED" itemvalue="1">
<f:selectitem itemlabel="BLUE" itemvalue="2">
</f:selectitem>
<h:selectoneradio id="color" style="font-family: Arial; font-weight: lighter; font-size: 12px;" onclick="showColor()">
<f:selectitem itemlabel="BLUE" itemvalue="2">"""
</f:selectitem>
<:selectoneradio id="color" style="font-family: Arial; font-weight: lighter; font-size: 12px;" onclick="showColor()">

And javascript function is

function showColor()
{
var tipoRelacion = document.getElementById("form:color").value;
alert("color" + tipoRelacion);

}

And what i get is that color is undefined. How can i get the value of the selected radio?
<h:selectoneradio id="color" style="font-family: Arial; font-weight: lighter; font-size: 12px;" onclick="showColor(this)">

<f:selectitem itemlabel="RED" itemvalue="1">
<f:selectitem itemlabel="BLUE" itemvalue="2">
</f:selectitem>
<h:selectoneradio id="color" style="font-family: Arial; font-weight: lighter; font-size: 12px;" onclick="showColor()">
<f:selectitem itemlabel="BLUE" itemvalue="2">
</f:selectitem>

And javascript function is

function showColor(obj)
{
var val = obj.value
alert("color" +val);

}

Thursday, July 5, 2007

在JSF中实现分页(二)

Reference URL Links:
http://cagataycivici.wordpress.com/2006/07/10/jsf_datatable_with_custom_paging/
http://wiki.apache.org/myfaces/WorkingWithLargeTables


前面一篇直接使用了
Myfaces中的两个Component完成了一个简单的分页,这里将会介绍一种On-demand loading的方法来进行分页,仅仅在需要数据的时候加载。

先来说一些题外话,为了实现这种方式的分页,公司里大约5-6个人做了半个多月的工作,扩展了dataTable,修改了dataScrollor,以及各种其他的方法,但是都不是很优雅。在上个月底的时候,在MyfacesMail List中也针对这个问题展开了一系列的讨论,最后有人总结了讨论中提出的比较好的方法,提出了以下的分页方法,也是目前实现的最为优雅的方法,也就是不对dataTabledataScrollor做任何修改,仅仅通过扩展DataModel来实现分页。

DataModel 是一个抽象类,用于封装各种类型的数据源和数据对象的访问,JSFdataTable中绑定的数据实际上被包装成了一个DataModel,以消除各种不同数据源和数据类型的复杂性,在前面一篇中我们访问数据库并拿到了一个List,交给dataTable,这时候,JSF会将这个List包装成 ListDataModel dataTable访问数据都是通过这个DataModel进行的,而不是直接使用List

接下来我们要将需要的页的数据封装到一个DataPage中去,这个类表示了我们需要的一页的数据,里面包含有三个元素:datasetSizestartRow,和一个用于表示具体数据的ListdatasetSize表示了这个记录集的总条数,查询数据的时候,使用同样的条件取count即可,startRow表示该页的起始行在数据库中所有记录集中的位置。


public class DataPage
{
private int datasetSize;
private int startRow;
private List data;

/**
* Create an object representing a sublist of a dataset.
*
*
@param datasetSize
* is the total number of matching rows available.
*
*
@param startRow
* is the index within the complete dataset of the first element
* in the data list.
*
*
@param data
* is a list of consecutive objects from the dataset.
*/

public DataPage( int datasetSize, int startRow, List data)
{
this .datasetSize = datasetSize;
this .startRow = startRow;
this .data = data;
}


/**
* Return the number of items in the full dataset.
*/

public int getDatasetSize()
{
return datasetSize;
}


/**
* Return the offset within the full dataset of the first element in the
* list held by this object.
*/

public int getStartRow()
{
return startRow;
}


/**
* Return the list of objects held by this object, which is a continuous
* subset of the full dataset.
*/

public List getData()
{
return data;
}

}
接下来,我们要对DataModel进行封装,达到我们分页的要求。该DataModel仅仅持有了一页的数据DataPage,并在适当的时候加载数据,读取我们需要页的数据。
/**
* A special type of JSF DataModel to allow a datatable and datascroller to page
* through a large set of data without having to hold the entire set of data in
* memory at once.
*


* Any time a managed bean wants to avoid holding an entire dataset, the managed
* bean should declare an inner class which extends this class and implements
* the fetchData method. This method is called as needed when the table requires
* data that isn't available in the current data page held by this object.
*


* This does require the managed bean (and in general the business method that
* the managed bean uses) to provide the data wrapped in a DataPage object that
* provides info on the full size of the dataset.

*/

public abstract class PagedListDataModel extends DataModel
{
int pageSize;
int rowIndex;
DataPage page;

/**
* Create a datamodel that pages through the data showing the specified
* number of rows on each page.
*/

public PagedListDataModel( int pageSize)
{
super ();
this .pageSize = pageSize;
this .rowIndex = - 1 ;
this .page = null ;
}


/**
* Not used in this class; data is fetched via a callback to the fetchData
* method rather than by explicitly assigning a list.
*/


public void setWrappedData(Object o)
{
if (o instanceof DataPage)
{
this .page = (DataPage) o;
}

else
{
throw new UnsupportedOperationException( " setWrappedData " );
}

}


public int getRowIndex()
{
return rowIndex;
}


/**
* Specify what the "current row" within the dataset is. Note that the
* UIData component will repeatedly call this method followed by getRowData
* to obtain the objects to render in the table.
*/


public void setRowIndex( int index)
{
rowIndex
= index;
}


/**
* Return the total number of rows of data available (not just the number of
* rows in the current page!).
*/


public int getRowCount()
{
return getPage().getDatasetSize();
}


/**
* Return a DataPage object; if one is not currently available then fetch
* one. Note that this doesn't ensure that the datapage returned includes
* the current rowIndex row; see getRowData.
*/

private DataPage getPage()
{
if (page != null )
{
return page;
}


int rowIndex = getRowIndex();
int startRow = rowIndex;
if (rowIndex == - 1 )
{
// even when no row is selected, we still need a page
// object so that we know the amount of data available.
startRow = 0 ;
}


// invoke method on enclosing class
page = fetchPage(startRow, pageSize);
return page;
}


/**
* Return the object corresponding to the current rowIndex. If the DataPage
* object currently cached doesn't include that index then fetchPage is
* called to retrieve the appropriate page.
*/


public Object getRowData()
{
if (rowIndex < 0 )
{
throw new IllegalArgumentException(
" Invalid rowIndex for PagedListDataModel; not within page " );
}


// ensure page exists; if rowIndex is beyond dataset size, then
// we should still get back a DataPage object with the dataset size
// in it
if (page == null )
{
page
= fetchPage(rowIndex, pageSize);
}


int datasetSize = page.getDatasetSize();
int startRow = page.getStartRow();
int nRows = page.getData().size();
int endRow = startRow + nRows;

if (rowIndex >= datasetSize)
{
throw new IllegalArgumentException( " Invalid rowIndex " );
}


if (rowIndex < startRow)
{
page
= fetchPage(rowIndex, pageSize);
startRow
= page.getStartRow();
}

else if (rowIndex >= endRow)
{
page
= fetchPage(rowIndex, pageSize);
startRow
= page.getStartRow();
}

return page.getData().get(rowIndex - startRow);
}


public Object getWrappedData()
{
return page.getData();
}


/**
* Return true if the rowIndex value is currently set to a value that
* matches some element in the dataset. Note that it may match a row that is
* not in the currently cached DataPage; if so then when getRowData is
* called the required DataPage will be fetched by calling fetchData.
*/


public boolean isRowAvailable()
{
DataPage page
= getPage();
if (page == null )
{
return false ;
}


int rowIndex = getRowIndex();
if (rowIndex < 0 )
{
return false ;
}

else if (rowIndex >= page.getDatasetSize())
{
return false ;
}

else
{
return true ;
}

}


/**
* Method which must be implemented in cooperation with the managed bean
* class to fetch data on demand.
*/

public abstract DataPage fetchPage( int startRow, int pageSize);

}

最后,我们需要在Backing Bean中加一些东西,调用业务逻辑,并将数据交给PagedListDataModel,来帮我们完成最后的分页工作。
public SomeManagedBean {
.


private DataPage getDataPage( int startRow, int pageSize) {
// access database here, or call EJB to do so
}


public DataModel getDataModel() {
if (dataModel == null ) {
dataModel
= new LocalDataModel(20);
}


return dataModel;
}


private class LocalDataModel extends PagedListDataModel {
public LocalDataModel( int pageSize) {
super (pageSize);
}


public DataPage fetchPage( int startRow, int pageSize) {
// call enclosing managed bean method to fetch the data
return getDataPage(startRow, pageSize);
}

}

这里面有一个getDataPage的方法,只需要把所有业务逻辑的调用放在这里就可以了,最后业务逻辑调用的结果返回一个List,总条数返回一个int型的count放到DataPage中去就可以了。

为了实现复用,把上面第三段的代码中的LocalDataModel类和getDataPage方法抽到BasePagedBackingBean中,把getDataPage方法改成:

protected abstract DataPage getDataPage(int startRow, int pageSize);

这样我们把所有需要分页的Backing Bean继承自这个抽象类,并实现getDataPage方法即可很容易的实现分页。

在具体应用中可以这么写:
protected DataPage getDataPage( int startRow, int pageSize)
{
List scheduleList
= scheduleService.getSchedulesByDate(scheduleDate, startRow, pageSize);
int dataSetSize = scheduleService.getSchedulesCountByDate(scheduleDate);
return new DataPage(dataSetSize, startRow, scheduleList);
}


在数据访问中,我们只需要取出我们需要行数的记录就可以了,这在hibernate中非常容易实现。

如果使用Criteria查询的话,只要加上:

criteria.setFirstResult(startRow);

criteria.setMaxResults(pageSize);

使用Query查询的话,只要加上

query.setFirstResult(startRow);

query.setMaxResults(pageSize);

并把两个参数传入即可。

我们还需要另外写一个CountDAO,取出相同查询条件的记录条数即可。

还要修改一下Backing Bean中与dataTable绑定的property,将返回类型由List改成DataModel,而第一篇中用到的页面不需要做任何修改就可以满足新的需求了。

里面最重要的是 PagedListDataModel fetchPage 这个方法,当满足取数据的条件时,都会调用它取数据,因为业务逻辑不同,不便于将业务逻辑的调用放在里面实现,于是将其作为抽象方法,将具体的实现放到具体的Backing Bean中进行,在BaseBackingBean中,实现了这个方法,调用了getDataPage(startRow, pageSize)这个方法,而在BaseBackingBean中,这个方法又推迟到更具体的页面中实现,这样,我们在具体的页面中只需要实现一个getDataPage(startRow, pageSize)这个方法访问业务逻辑。

大功告成,这个实现把前面遇到的两个问题都解决了, On-demand loading 是没有问题了,因为只有在首次读取和换页的时候DataModel才会向数据库请求数据,虽然在JSF的生命周期中多次调用与dataTable绑定的方法,但是因为每次业务逻辑请求以后,数据都会存放在DataPage中,如果里面的数据满足需求的话,就不再请求访问数据库,这样多次访问数据库的问题也解决了。

虽然这样的话,dataScrollorTag使用起来还是很复杂,通常在同一个项目中,我们只会使用一种样式的分页导航,不过没关系,我们只需要修改以下DataScrollorRender Kit,把一些可以定义的值固定下来,再定义一个TLD文件,就可以在项目中使用简化版的Tag了。

这个方法一开始发布在MyfacesWiki中,http://wiki.apache.org/myfaces/WorkingWithLargeTables,那里很少有人关注到,大家有兴趣可以看看原文,本文只是对这种方法做一些简单的介绍,并非自创,希望大家能够多多关注开源社区,因为那里有最新最好的东西。

Nightly Build服务器中拿到的12.27Myfaces包,发现里面扩充了很多新的Component,只是并没有正式发布,大家有兴趣的话可以研究研究。