2008年(3500)
分类:
2008-05-04 20:28:35
——新的J2EE
RowSet实现为JSP提供了可更新的断接结果集
目前通过网络(物理连接的或无线的)访问数据库存在的主要问题是,无法保证与数据库的连接在任何时候都没有中断。由于其它资源的限制,即使可以获得可靠的数据库连接,也存在着如何合理地使用这些连接的问题。直到最近的一段时间,解决这些问题的唯一途径仍然是那些需要解决这一问题的Java高手自己动手开发一个缓冲区管理层。
本文介绍一个新的使用JDBC 2.0的类,它可以简化数据库访问代码、降低使用JDBC的连接数并且能够提供JDBC 1.0结果集所不能提供的结果集滚动。这个类可以从Sun的早期数据库访问版本获得,它由三个JDBC
RowSet接口的实现构成。我们这里只介绍其中的一个——CachedRowSet——用来作为简单JSP应用(就是使用Enterprise
JavaBeans有些大材小用时)的数据模型。
可以把CachedRowSet看成是断接的(disconnected)结果集(ResultSet)。它是javax.sql.RowSet的一个早期实现,由于javax.sql.RowSet是java.sql.ResultSet接口的扩展,您可能已经对其大部分方法很熟悉了。RowSet从JavaBeans组件对象模型的角度支持JDBC
API。它提供了标准JDBC 2.0 ResultSet所具有的所有方法和特性,但并不需要持续使用数据库连接。象其它JavaBean一样,javax.sql.RowSet的实现也可以被串行化。这样,就可以将ResultSets串行化而后发送给远程客户端,进行更新操作后在发送回服务器。
CachedRowSet的发布已经有一段时日了,因为现在已经有很多WEB开发者对Microsoft的ActiveX
Data Objects (ADO)一类的可更新结果集非常熟悉了。很多条目/更新(entry/update)表单需要提供在一个记录集上进行向后和向前滚动的能力。在理想情况下,用户仅仅在缓冲状态下的结果集进行更新而后通过存储命令一次性将所有更新结果存储在数据库中。虽然JDBC
2.0 ResultSet接口支持这种类型的滚动,但用户浏览记录时需要每一个会话都维护与数据库的一个开放连接。为了最大限度地利用数据库资源,您应该在绝对需要时才使用数据库连接。在暂时不需要数据库连接时,应该尽快将其释放回连接池。CachedRowSet提供了这种伸缩性,它仅仅在执行查询和更新时才需要一个数据库连接。
我预计javax.sql.RowSet的第三方实现会很快出现,但与此同时,对于想熟悉新的J2EE平台javax.sql.RowSet接口的开发者而言,CachedRowSet是很有用的。
目前您可以从Java开发者连接(http://developer.java.sun.com/developer/earlyAccess/crs)
下载CachedRowSet的实现。下载并解压缩安装文件后,将"rowset.jar"文件放到您的类目录下。CachedRowSet在sun.jdbc.rowset包中。
创建一个CachedRowSet
CachedRowSet事实上是一个JavaBean。它支持允许自身连接一个数据库并获取数据的属性。下面的表格描述了需要在没有预先存在数据库连接的情况下初始化CachedRowSet的一些属性:
属性 描述
Username Database的用户名
Password Database的用户口令
URL Database JDBC URL如:jdbc:odbc:mydsn
Command SQL查询语句
CachedRowSet数据库连接属性
因为是JavaBean,所以在创建新的CachedRowSet对象实例时可以简单地使用却省的构造器:
CachedRowSet
crs = new CachedRowSet();
一旦已经创建了这个实例,就可以通过调用其属性设置方法来初始化这个bean。CachedRowSet是一个bean的事实使得在JSP中使用它也非常地方便。要在JSP中创建一个CachedRowSet的实例,使用标准的JSP
useBean标签就可以了。UseBean标签会创建一个新的实例,这个实例会作为一个会话(session)值放置在相应的session中并且作为一个脚本变量供JSP进行处理。您可以通过很多方法在useBean标签中对CachedRowSet进行初始化。其中一些方法依赖于您的应用服务器是否具有连接池功能,而另外一些则仅仅需要JDBC
1.0驱动器。清单1说明了在JSP页面中创建和初始化CachedRowSet的一个最明了的方法:
清单1:简单明了的CachedRowSet初始化方法
scope="session">
<%
// load database driver
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
// initialize our CachedRowSet bean
Contacts.setUsername("dbuser"); // example
userid
Contacts.setPassword("dbpassword"); // example
password
Contacts.setUrl("jdbc:odbc:ContactDB");
// example DSN
Contacts.setCommand("SELECT name, telephone from
Contacts");
Contacts.execute();
%>
当已经使用上述方法完成创建时,CachedRowSet会在需要的时候获得自己的数据库连接。在useBean的开始和结束标签之间的
Java代码小脚本(scriptlet)仅仅在useBean第一次将这个bean放置在会话中时执行一次。由于CachedRowSet已经在session中了,随后的页面访问会跳过useBean标签和其中包含的内容。
对于具有J2EE连接池支持的JSP容器来说,可以使用dataSourceName属性代替用户名、密码和URL属性的设置。如果设置了dataSourceName,CachedRowSet会试图从JNDI获得一个连接而不是单独创建一个。这种情况需要编写的代码就更少了,因为应用服务器的连接池已经为您建立了连接。
清单2:从一个J2EE连接池进行初始化
<%
// initialize our CachedRowSet bean using dataSourceName
// property
Contacts.setDataSourceName("Databases/ContactsDB/Datasource");
Contacts.setCommand("SELECT name, telephone from
Contacts");
Contacts.execute();
%>
由于同时访问网站的用户数量可能非常巨大,从J2EE连接池创建CachedRowSet要比直接创建连接好得多。DataSourceName属性告诉CachedRowSet在JNDI命名空间的什么地方可以找到希望的连接。一旦调用了execute()方法,CachedRowSet就会从JNDI取得一个javax.sql.DataSource的实例。DataSource对象为提供一个到RowSet的JDBC连接。
除了靠CachedRowSet获得一个连接外,还可以将一个现有的连接作为参数传递给execute()方法。清单3中的例子同清单2中一样也是从J2EE连接池获取连接,所不同的是,这次的JNDI查找是显式的:
清单3:通过传递一个现有的连接进行初始化
<%
// get connection from pool
InitialContext ctx = new InitialContext();
javax.sql.DataSource ds =
(javax.sql.DataSource)ctx.lookup("Databases/ContactsDB/DataSource");
java.sql.Connection con = ds.getConnection();
Contacts.setCommand("SELECT name, telephone from
Contacts");
// supply the connection to the RowSet
Contacts.execute(con);
%>
不同应用服务器的连接池JNDI查找名是不一样的。您可以参考您所使用的应用服务器的说明文档,以便给连接池一个合适的JNDI查找模式。
在JSP中显示CachedRowSet 属性
初始化CachedRowSet以后,就可以象java.sql.ResultSet对象一样使用它了。正如先前所指出的,CachedRowSet仅仅是java.sql.ResultSet接口的另外一个实现而已。您可以通过将游标放置在目标行而后使用getString()方法访问CachedRowSet属性(假设对应的数据类型是varchar)。每一种JDBC数据类型都有对应的get方法。如果您不清楚是什么数据类型,您可以使用getObject()来替代。通过索引号或原始的ResultSet指定列:
Contacts.getString(1);
如果您采用象清单1中的方法包含useBean标签的话,您就可以在您的页面的任何位置通过该标签的脚本ID访问CachedRowSet。下面就是如何使用HTML提交表单域显示一个CachedRowSet列的实例:
name="ContactName"
value="<%=Contacts.getString(1)%>"/>
为了获得属性,您可以显示CachedRowSet的其它信息,如当前行号:
Record <%=Contacts.getRow()%>
of <%=Contacts.size()%>
对于显示CachedRowSet属性来说,Navigation也是很重要的。CachedRowSet支持ResultSet接口所声明的所有navigation命令。甚至包括诸如previous()、first()和last()等新的JDBC
2.0方法。
目前使用的标准JDBC版本是JDBC 1.0,Sun并没有将其JDBC驱动器更新为2.0版本。但是,由于在客户端对数据进行了缓存,CachedRowSet为JDBC1.0提供了前向和后向的滚动能力。这个优越的特性可以让开发者从为记录浏览应用写自己的ResultSet
缓冲对象的困境中解放出来。要从您的JSP中进行导航,仅仅接受请求与响应的命令参数就可以了:
// process navigation commands
if ( req.getParameter("next") != null )
{
if (! Contacts.next() )
Contacts.last();
} else if ( req.getParameter("prev") !=
null) {
if (! Contacts.previous())
Contacts.first();
}
每一个导航方法都根据处理的成功与否返回true或false。如果失败了,游标必须返回到有效的位置。可以通过提交按钮发送导航参数:
对CachedRowSet执行更新
我最喜欢的CachedRowSet特性是它可以自动完成对数据库的更新操作。熟悉ASP和Microsoft
ADO开发的人也许会对这个特性很感兴趣。CachedRowSet可以大大减少开发人员所书写的SQL语句数量,并且在一个操作中就可以完成联合更新。联合更新对只有有限的数据库连接并且可能在连接上产生瓶颈的WEB应用来说非常有用。对大多数CachedRowSet应用来说,您只需要提供初始的SQL查询语句。最少只需要CachedRowSet上的三个方法就可以实现一个更新操作,但这些方法必须按照指定的顺序进行调用。首先,假设游标定位在正确的位置,
调用ResultSet的一个更新方法。这依赖于示例数据库中的列类型,更新可能如下所示:
Contacts.updateString(1, "new value");
如果需要,您可以在RowSet中为需要的列重复上面的类似代码。
然后,在移动游标位置之前,RowSet必须知道您是否希望提交对当前列的更改:
Contacts.updateRow();
上述代码并没有将更改发送到数据库,而是仅仅将更改缓存到RowSet的缓存区。当用户进行记录更新时,并不需要捆绑数据库连接。用户仅仅在需要将RowSet的所有更新提交给数据库时才需要连接。
第三也是最后一步是调用acceptChanges(),它将所有的更改一起提交给数据库。还是假设您采用清单1中的方法创建的CachedRowSet,下面的代码会提交包括最后一次以前的所有更新操作:
Contacts.acceptChanges();
如果您是采用清单3中的方法初始化和创建CachedRowSet的,您必须指定连接对象:
Contacts.acceptChanges(connection);
在JSP页面中,这个操作可以与一个请求参数进行关联。它允许用户决定什么时候提交更新:
if ( req.getParameter("save") != null )
{
Contacts.acceptChanges();
}
CachedRowSet同样可以执行插入和删除操作。虽然向CachedRowSet插入记录非常简单,但显得有些不太直观。在插入一行以前,您必须将游标放置在一个称为“插入行”的特殊位置。
下面的代码说明如何将一行数据插入到CachedRowSet:
rowSet.moveToInsertRow(); // move
to construct an insert row
rowSet.updateString(1, "New
Contact"); // initialize name
rowSet.updateString(2, "(111)
111-1111"); // initialize phone
rowSet.insertRow(); // insert the
row
rowSet.moveToCurrentRow(); // move
back to previous cursor position
moveToInsertRow()将游标移动到可能包含初始列值的空白行。调用insertRow()将新行插入到游标最近位置的后面。然后,在调用其它导航命令之前调用moveToCurrentRow()重置游标位置。最后,必须为RowSet调用acceptChanges()以更新数据库。将它应用到JSP中可能需要一些修改,大多数记录的entry/edit表单按照下面的步骤插入行: