7.1 数据库应用编程
7.2 会话编程
7.3 PageListSevlet技术
WebSphere应用服务器中包括它自己的软件包,该软件包用于扩展和添加 Java Servlet API。这些扩展和添加简化了维护用户信息、个性化 Web 页面和分析 Web 站点使用情况。WebSphere应用服务器API 的 Javadoc 安装在产品的 doc/apidocs 目录下。WebSphere应用服务器 API 软件包为:
1.com.ibm.servlet.personalization.sessiontracking
对 JDSK 的这种 WebSphere应用服务器 扩展记录了分派页面,该页面引导访问者进入 Web 站点, 该设备同时在站点内跟踪访问者的位置,并关联了会话和用户标识符。IBM 还将会话群集支持添加到 API 中。
2.com.ibm.servlet.personalization.userprofile
提供维护有关 Web 访问者的详细信息的方法,并在 Web 应用程序合并这些信息, 以便能够提供一个个性化的用户经历。
3.com.ibm.servlet.connmgr
使 Servlet 能与连接管理器进行通信,该连接管理器对打开的数据库服务器和 JDBC 从属数据服务器产品的连接的缓冲池进行维护。 当 Servlet 从缓冲池接收到连接时,它可以用它的 API 直接与数据服务器进行通信。
4.com.ibm.db
将类包括在内,以简化对关系数据库的访问并同时提供增强访问功能,如结果高速缓存、 通过执行高速缓存进行更新和查询参数支持。
5.com.ibm.servlet.personalization.sam
使您能用站点活动监控程序(Site Activity Monitor)注册自己的 Web 应用程序,它是一个提供给您动态、实时查看您 Web 站点活动的applet。
6.com.ibm.servlet.servlets.personalization.util
包括特殊的 Servlet,它允许 Web 站点管理员动态地登记公告牌,并允许 Web 站点访问者之间交换信息。
7.1 数据库应用编程
1.Servlet 如何使用连接管理器
所有使用连接管理器的 Servlet 都将执行下列步骤:
(1) 创建连接规范:Servlet 为连接到基本数据服务器准备了一个用以标识必需信息的规范对象。其中某些信息是仅针对于特定数据服务器的,而一般信息则适用于所有下层数据服务器。Servlet 仅准备一次规范并将它应用于所有请求,或者为每个用户请求准备一份新的规范。如果要为每个用户请求准备新的规范,则必须在第 3 步(使用规范)之前,执行该操作。
(2) 获得连接管理器:Servlet 为与连接管理器交流而获取有关连接管理器的引用。这一操作只需在 Servlet 的生命期中执行一次。
(3) 获得与连接管理器的连接:Servlet 请求连接管理器以连接到特定数据服务器,该数据服务器使用第 1 步中准备的连接规范。返回的连接对象来自于连接管理缓冲池,并且是在连接管理器 API 中定义类别的实例,它不是一个来自于数据服务器下属 API 集合中的类的对象。先调用它来暂时连接一个连接管理器或一个 CM 连接。通常 Servlet 为每个用户请求获得一个 CM 连接。
(4) 使用 CM 连接以访问预先建立的数据服务器连接:Servlet 调用第 3 步中返回 CM 连接的方法,检索定义在下层数据服务器的 API 集合中的对象。调用该数据服务器连接对象(或暂时调用一个数据连接)以从 CM 连接中将它分辨出来。这个数据连接不同于 CM 连接,它是来自下层数据服务器 API 集合。这个数据连接不是为 Servlet 而创建 -- 相反这个 Servlet 使用预先建立的数据连接因为它拥有一个缓冲池到缓冲池的连接。将使用来自下层数据服务器 API 集合的方法,以将该数据连接用于与数据服务器的交互。例如,在 Servlet 代码示例中下层数据服务器是一个 JDBC 数据库,而且数据连接对象则是来自 JDBC API 的连接类。
(5) 和数据服务器的相互作用:Servlet 与数据服务器相互作用(如:检索数据和更新数据)使用的是数据连接对象的方法。这些方法针对的是下层数据服务器,因为数据连接实际上来自下层数据服务器的 API 集合。不同的下层数据服务器的数据连接具有不同的方法。方法的全部文档可用于特定数据服务器产品的 API 文档。例如为了实现一个 JDBC 数据连接,可能需要查看关于 java.sql 程序包的文档,以及任何正使用的启用 JDBC 的关系数据库文档。如果在同一用户请求中对多个数据服务器的交互使用数据连接,可能希望在 Servlet 中仍有关联的 CM 连接的其它交互之前进行验证。要验证 Servlet 中仍有连接,Servlet将依次调用 verifyIBMConnection() 方法以使连接管理器检查连接的验证时间标记。如果 Servlet 中仍有连接,则将最近使用时间标记自动更新为当前时间,并作为调用 VerifyIBMConnection() 方法的一部分。然后 Servlet 使用连接来与数据服务器进行通信,确认连接生效。当 Servlet 完成连接时,它将连接释放回连接缓冲池。连接管理器将正在使用标记设置为假,并将验证和最近使用时间标志设置为当前时间。
(6) 释放连接::Servlet 把 CM 连接返回到连接管理器缓冲池,释放连接以供另一个 Servlet 或该 Servlet 的另一个请求使用。
(7) 预备和发送响应:Servlet 准备响应,并将该响应发回给用户。在该步骤中可能不会使用任何连接管理器 API。
2.CM示例
// *******************************************************************
// * IBMConnMgrTest.java - 连接管理器示例的Sevlet,简称CM示例的servlet
// *******************************************************************
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;
import com.ibm.servlet.connmgr.*; // 使用了IBM的连接管理器软件包com.ibm.servlet.connmgr
public class IBMConnMgrTest extends HttpServlet {
// Use to communicate with connection manager.
static IBMConnMgr connMgr = null;
// Use later in init() to create JDBC connection specification.
static IBMConnSpec spec = null; // the spec
static String DbName = null; // database name
static String Db = "db2"; // JDBC subprotocol for DB2
static String poolName = "JdbcDb2"; // from Webmaster
static String jdbcDriver = "COM.ibm.db2.jdbc.app.DB2Driver";
static String url = null; // constructed later
static String user = null; // user and password could
static String password = null; // come from HTML form
static String owner = null; // table owner
// Name of property file used to complete the user, password,
// DbName, and table owner information at runtime。
static final String CONFIG_BUNDLE_NAME = "login";
// ****************************************************************
// * Initialize Servlet when it is first loaded *
// ****************************************************************
public void init(ServletConfig config)throws ServletException {
super.init(config);
try {
// Get information at runtime (from an external property file
// identified by CONFIG_BUNDLE_NAME) about the database name
// and the associated database user and password. This
// information could be provided in other ways. It could be
// hardcoded within this application, for example.
PropertyResourceBundle configBundle =
(PropertyResourceBundle) PropertyResourceBundle.getBundle(CONFIG_BUNDLE_NAME);
DbName = configBundle.getString("JDBCServlet.dbName");
url = "jdbc:" + Db + ":" + DbName;
user = configBundle.getString("JDBCServlet.dbUserid");
password = configBundle.getString("JDBCServlet.dbPassword");
owner = configBundle.getString("JDBCServlet.dbOwner");
}
catch (Exception e) {
System.out.println("read properties file: " + e.getMessage());
}
try {
// 第一步:创建连接规范
spec = new IBMJdbcConnSpec(poolName, true, jdbcDriver, url, user, password);
// 第二步:获得连接管理器
connMgr = IBMConnMgrUtil.getIBMConnMgr();
}
catch (Exception e) {
System.out.println("set connection spec, get connection manager: " +
e.getMessage());
}
}
// ****************************************************************
// * Respond to user GET request *
// ****************************************************************
public void doGet(HttpServletRequest req, HttpServletResponse res) {
IBMJdbcConn cmConn = null;
Connection dataConn = null;
Vector firstNameList = new Vector();
try {
// 第三步:获得与连接管理器的连接
cmConn = (IBMJdbcConn)connMgr.getIBMConnection(spec);
// 第四步:使用 CM 连接以访问预先建立的数据服务器连接
dataConn = cmConn.getJdbcConnection();
//第五步:和数据服务器的相互作用
Statement stmt = dataConn.createStatement();
String query = "Select FirstNme " +
"from " + owner + ".Employee " +
"where LASTNAME = 'PARKER'";
ResultSet rs = stmt.executeQuery(query);
while(rs.next())
{
firstNameList.addElement(rs.getString(1));
}
// Invoke close() on stmt, which also closes rs, freeing
// resources and completing the interaction. You must
// not, however, close the dataConn object. It must
// remain open and under the control of connection manager
// for possible use by other requests to this servlet or to other servlets.
stmt.close();
} catch (Exception e) {
System.out.println("get connection, process statement: " +
e.getMessage());
}
// 第六步:释放连接
finally {
if(cmConn != null) {
try {
cmConn.releaseIBMConnection();
} catch(IBMConnMgrException e) {
System.out.println("release connection: " + e.getMessage());
}
}
}
// 第七步:预备和发送响应
// say Hello to everyone whose last name is 'Parker'
res.setContentType("text/html");
// Next three lines prevent dynamic content from being cached
res.setHeader("Pragma", "no-cache");
res.setHeader("Cache-Control", "no-cache");
res.setDateHeader("Expires", 0);
try {
ServletOutputStream out = res.getOutputStream();
out.println("<HTML>");
out.println("<HEAD><TITLE>Hello DBWorld</TITLE></HEAD>");
out.println("");
if(firstNameList.isEmpty()) {
out.println("<H1>Nobody named Parker</H1>");
}
else {
for(int i = 0; i < firstNameList.size(); i++)
{
out.println("<H1>Hello " +
firstNameList.elementAt(i) + " Parker</H1>");
}
}
out.println("</BODY></HTML>");
out.close();
} catch(IOException e) {
System.out.println("HTML response: " + e.getMessage());
}
}
}
3.连接管理器 API
结合上述示例,下面讨论部分的连接管理器 API。
(1) IBMJdbcConnSpec 类
创建该类的规范对象以将连接规范记录到期望的数据服务器上。这通常是在步骤 2 中执行的。规范对象实际上并没有设置规范,但是它却被另一个方法当作一个变量使用,该方法用于设置规范。请在 IBMConnMgr 类中参阅 getIBMConnection() 方法。
目前,连接服务器仅支持遵从 JDBC 的数据服务器,因此只有 JDBC 规范的类是可用的。当支持其它数据服务器时,其它规范类也将变得可用。在建立规范对象后,请使用 get 和 set 方法指定连接要求。 通常,将在规范对象的初始建立过程中指定所有的要求。
public IBMJdbcConnSpec(String poolName,boolean waitRetry,String dbDriver,String url, String user,String password)
缓冲池名称:包含希望使用的连接类型的连接管理器缓冲池。请查询 Web 管理员以获知缓冲池名称。
等待重试:如果当前缓冲池中没有可用的连接时,是否要等待连接释放(若要等待,请指定为真)。 Web 管理员使用“连接超时”参数来设置等待整个缓冲池的时间长度,因此如果在给定的时间内仍不能使用连接,请求将告失败。如果缓冲池中的连接不可用,请指定为假表示立即失败。
数据库驱动程序:由特定的 JDBC 产品多提供的驱动程序类,或提供 JDBC-ODBC 桥接器的驱动程序名。请参阅 Web 管理员或数据库管理员以获得驱动程序名。
URL :数据库 URL。
用户:作为代表进行连接的数据库用户。
口令:用户的口令。
(2)IBMConnMgrUtil类
使用该类的一种方法以获得有关连接管理器的引用。这通常是在步骤 2 步中执行的。将使用获得的引用来和连接管理器进行通讯并使用它所提供的服务。这里只讨论一个静态方法:
public static IBMConnMgr getIBMConnMgr();
该方法将引用返回到连接管理器。
(3) IBMConnMgr 类
正在运行的连接管理器实例是该类的一个实例。将在步骤 2 步中获得有关连接管理器的参考。 Servlet 从未创建一个连接管理器的实例,而是使用一个现有实例的参考。这里只讨论该类的一种方法:就是用于从缓冲池获得一个 CM 连接的 getIBMConnection() 方法(在步骤 3 中使用)。
public IBM Connection get IBM Connection (IBM ConnSpec connSpec) throws
IBM ConnMgr Exception
参数connSpec包含了对一个专用下层数据服务器的详细连接要求。在示例中,它是 IBMJdbcConnSpec 对象(步骤 1 中创建的)。该方法从连接管理器缓冲池返回一个 IBMConnection 对象。
如果 CM 连接是可用的,则 getIBMConnection() 方法为 Servlet 从缓冲池中获得一个 CM 连接。仅获通过的参数是连接的规范对象(在步骤 1 中创建的)。如果一个 CM 连接不是马上可用,并且如果规范对象中的 waitRetry 设置为真,则 Servlet 可以等待 CM 连接变成可用为止。等待的时间长度是由 Web 管理员用“连接超时”参数设置的。Web 管理员还可以禁止等待或不确定地延长等待时间。如果在等待周期过后 CM 连接仍不可用,getIBMConnection() 方法将报告 IBMConnMgrException 异常。如果 waitRetry 设置为假,获取 CM 连接的失败将导致 getIBMConnection() 立刻报告异常。
返回的 IBMConnection 对象是一个“普通”连接对象,必须将它造型成您需访问的下层数据服务器的特定连接对象。例如,在示例 Servlet 中,返回的 IBMConnection 对象被造型成一个 IBMJdbcConn 对象。缺少了适当的造型,连接对象就不能使用其方法在步骤 5 中到达下层数据服务器。
(4) IBMJdbcConn 类
考虑到要访问的下层数据服务器,需将步骤 3 中检索的 IBMConnection 对象造型成一个连接对象。否则,就不存在与下层数据服务器关联的 API 访问。当前,只有一个这样的连接类可用,供访问 JDBC 数据服务器的 IBMJdbcConn 类。通过 IBMJdbcConn 对象访问 JDBC 服务器的工作通常是在步骤 4 中建立的。IBMJdbcConn 类只关心一种方法:
public Connection getJdbcConnection()
该方法将一个连接对象返回至下层 JDBC 数据服务器。连接类来自 JDBC API ,并由 java.sql 软件包存档。连接类的方法让您可使用 JDBC 数据服务器进行交互。该文档的其它部分较多地使用了连接类,而非数据连接,以将其和 CM 连接区分开。 CM 连接不是下层数据服务器 API 集合的一部分。
(5) IBMConnection 类
IBMJdbcConn 类是 IBMConnection 类的一个扩展。因此,IBMConnection 中的多数方法也包括在 IBMJdbcConn 类的实例中。这里讨论的是 IBMConnection 类(即 IBMJdbcConn 类)的两种方法:
public boolean verifyIBMConnection() throws IBMConnMgrException
使用该方法以验证 CM 连接是否仍然有效。可步骤 5 中可选地使用该方法。如果 CM 连接在指定的时间内一直是非活动的(由 Web 管理员检查以确定该由连接管理器配置的操作),则连接管理器可能从 Servlet 中移走 CM 连接。如果在一个用户请求中对几个交互操作使用数据连接,可能希望在每个交互操作在缓冲池中检查 Servlet 中是否仍有关联的 CM 连接之前,调用 verifyIBMConnection() 方法。如果 Servlet 中仍有连接,该方法将返回真,且调用的方法也将重新设置一个最近使用时间标记。 如果预见 Servlet 与数据服务器的交互操作(从一个用户请求中)将在几秒中之内完成,并且如果连接管理器缓冲池的“最长周期”参数至少长达有几分钟,则可能不需要使用 verifyIBMConnection() 方法,请求将在连接管理器移走连接的很早以前就完成。
public void releaseIBMConnection() throws IBMConnMgrException
使用该方法将 CM 连接返回到缓冲池。可在步骤 6 中可选地使用它。当一个 Servlet 不再需要 CM 连接对象时, Servlet 便会使用这种方法将连接释放回缓冲池。这应该在每个用户请求的最后完成。
4.数据访问 JavaBean
访问遵从 JDBC 关系数据库的 Java 应用程序(和 Servlet)通常使用 java.sql 软件包中的类和方法来访问数据。数据访问JavaBeans 是遵守JavaBeans 规约的 Java 类,这种类是用来存取数据库中数据。由于数据访问类是 JavaBean,可以使用在集成开发环境(IDE)下, 如 IBM 产品 VisualAge for Java,允许程序员用可视方法操纵 Java 类,而非编辑 Java 代码的行。由于 JavaBean 也是 Java 类,所以程序员在写 Java 代码时,可以象使用普通的类一样使用 JavaBean。
可以使用软件包com.ibm.db 中的类和方法,来替代使用 java.sql 软件包。软件包 com.ibm.db 提供了:
(1) 查询结果的高速缓存:可以立刻检索所有的 SQL 查询结果并将其放入高速缓存。应用程序(或 Servlet)可以在高速缓存中向前和向后移动或直接转移到高速缓存中的任何结果行。比较它与 java.sql 软件包的灵活性,java.sql 软件包每次从一个数据库中检索一行(仅在向前方向中),而且新的检索行覆盖上一次的检索行,除非又写入了扩展功能代码。对于大量的结果集合,数据访问 JavaBean 提供了检索和管理包的方法,完整的结果设置的子集。
(2) 通过结果高速缓存更新:Servlet 可以使用标准的 Java 语句(而不是 SQL 语句)修改、添加或删除结果高速缓存中的行。更改至可以传播到下属关系表格的高速缓存。
(3) “查询”参数支持:用参数替代某些实际值,以将基本 SQL 查询定义为 Java 字符串。当查询运行时,数据访问 JavaBean 提供了用运行时生成的可用值来代替参数的方法。例如,参数值可能由用户用 HTML 表格提交。
(4) 元数据支持:StatementMetaData 对象包含基本 SQL 查询。可以将高级别的数据(元数据)加入对象,以帮助将参数传递到查询中并使用返回的结果。“查询”参数可以是便于 Java 程序使用的 Java 数据类型。当查询运行时,会使用元数据规格将参数自动转换成适合 SQL 数据类型的表格。为了阅读查询结果,元数据规格可以将 SQL 数据类型自动转换成最便于 Java 应用程序使用的 Java 数据类型。
5. DA示例
// *******************************************************************
// * IBMDataAccessTest.java - 使用数据访问 JavaBean 的示例Servlet,简称DA示例的Servlet
// *******************************************************************
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;
import java.math.BigDecimal;
import com.ibm.db.*; // 使用了IBM的软件包com.ibm.db
import com.ibm.servlet.connmgr.*; // 继续使用IBM的连接管理器软件包com.ibm.servlet.connmgr
public class IBMDataAccessTest extends HttpServlet {
// Use to communicate with connection manager.
static IBMConnMgr connMgr = null;
// Use later in init() to create JDBC connection specification.
static IBMConnSpec spec = null; // the spec
static String DbName = null; // database name
static String Db = "db2"; // JDBC subprotocol for DB2
static String poolName = "JdbcDb2"; // from Webmaster
static String jdbcDriver = "COM.ibm.db2.jdbc.app.DB2Driver";
static String url = null; // constructed later
static String user = null; // user and password could
static String password = null; // come from HTML form
static String owner = null; // table owner
// Name of property file used to complete the user, password,
// DbName, and table owner information at runtime. ".properties"
static final String CONFIG_BUNDLE_NAME = "login";
// Single metaData object, used by all user requests, will be
// fully defined in the init() method when the servlet is loaded.
com.ibm.db.StatementMetaData metaData = null; // 与CM示例不同,增加了变量metaData
// ****************************************************************
// * Initialize Servlet when it is first loaded *
// ****************************************************************
public void init(ServletConfig config)throws ServletException {
super.init(config);
try {
PropertyResourceBundle configBundle =
(PropertyResourceBundle) PropertyResourceBundle.getBundle(CONFIG_BUNDLE_NAME);
DbName = configBundle.getString("JDBCServlet.dbName");
url = "jdbc:" + Db + ":" + DbName;
user = configBundle.getString("JDBCServlet.dbUserid");
password = configBundle.getString("JDBCServlet.dbPassword");
owner = configBundle.getString("JDBCServlet.dbOwner");
} catch (Exception e) {
System.out.println("read properties file: " + e.getMessage());
}
try {
// 第一步:创建连接规范
spec = new IBMJdbcConnSpec(poolName, true, jdbcDriver, url, user, password);
// 第二步:获得连接管理器
connMgr = IBMConnMgrUtil.getIBMConnMgr();
}
catch (Exception e) {
System.out.println("set connection spec, get connection manager: " +
e.getMessage());
}
// 增加数据访问JavaBeans代码
String sqlQuery = "SELECT ID, NAME, DEPT, COMM " +
"FROM " + owner + ".STAFF " +
"WHERE ID >= ? " +
"AND DEPT = ? " +
"ORDER BY ID ASC";
// Start defining the metaData object based on the query string.
metaData = new StatementMetaData();
metaData.setSQL(sqlQuery);
try {
// Add some more information to the metaData to make Java
// programming more convenient. The addParameter() method allows
// us to supply an input parameter for the query using a
// convenient Java datatype, doing a conversion to the datatype
// actually needed by SQL. The addColumn() method makes things
// convenient in the other direction, retrieving data in a
// datatype convenient for Java programming, doing a conversion
// from the underlying SQL datatype. The addTable() method
// identifies the relational table and makes it possible for
// result cache changes to be folded back onto the table.
metaData.addParameter("idParm", Integer.class, Types.SMALLINT);
metaData.addParameter("deptParm", String.class, Types.SMALLINT);
metaData.addColumn("ID", String.class, Types.SMALLINT);
metaData.addColumn("NAME", String.class, Types.VARCHAR);
metaData.addColumn("DEPT", Integer.class, Types.SMALLINT);
metaData.addColumn("COMM", BigDecimal.class, Types.DECIMAL);
metaData.addTable("STAFF");
} catch(DataException e) {
System.out.println("set metadata: " + e.getMessage());
}
}
// ****************************************************************
// * Respond to user GET request *
// ****************************************************************
public void doGet(HttpServletRequest req, HttpServletResponse res) {
IBMJdbcConn cmConn = null;
Connection dataConn = null;
SelectResult result = null;
Try {
// 第三步:获得与连接管理器的连接
cmConn = (IBMJdbcConn)connMgr.getIBMConnection(spec);
// 第四步:使用 CM 连接以访问预先建立的数据服务器连接
dataConn = cmConn.getJdbcConnection();
//第五步:和数据服务器的相互作用,但与CM示例不同
// Make use of the externally managed connection dataConn gotten
// through the connection manager. Our dataAccessConn object
// should be local (a new object for each request), since there
// may be multiple concurrent requests.
DatabaseConnection dataAccessConn = new DatabaseConnection(dataConn);
// Begin building our SQL select statement - it also needs to be
// local because of concurrent user requests.
SelectStatement selectStatement = new SelectStatement();
selectStatement.setConnection(dataAccessConn);
// Attach a metadata object (which includes the actual SQL
// select in the variable sqlQuery) to our select statement.
selectStatement.setMetaData(metaData);
// Make use of the facilities provided through the metadata
// object to set the values of our parameters, and then execute
// the query. Values for dept and id are usually not hardcoded,
// but are provided through the user request.
String wantThisDept = "42";
Integer wantThisId = new Integer(100);
selectStatement.setParameter("deptParm", wantThisDept);
selectStatement.setParameter("idParm", wantThisId);
selectStatement.execute();
// The result object is our cache of results.
result = selectStatement.getResult();
// Try an update on the first result row. Add 12.34 to the
// existing commission, checking first for a null commission.
BigDecimal comm = (BigDecimal)result.getColumnValue("COMM");
if(comm == null) {
comm = new BigDecimal("0.00");
}
comm = comm.add(new BigDecimal("12.34"));
result.setColumnValue("COMM", comm);
result.updateRow();
// Close the result object - no more links to the relational
// data, but we can still access the result cache for local
// operations, as shown in STEP 7 below.
result.close();
} catch (Exception e) {
System.out.println("get connection, process statement: " +
e.getMessage());
}
// 第六步:释放连接
finally {
if(cmConn != null) {
try {
cmConn.releaseIBMConnection();
} catch(IBMConnMgrException e) {
System.out.println("release connection: " + e.getMessage());
}
}
}
// 第七步:预备和发送响应,与CM示例有区别。
res.setContentType("text/html");
// Next three lines prevent dynamic content from being cached
res.setHeader("Pragma", "no-cache");
res.setHeader("Cache-Control", "no-cache");
res.setDateHeader("Expires", 0);
try {
ServletOutputStream out = res.getOutputStream();
out.println("<HTML>");
out.println("<HEAD><TITLE>Hello DBWorld</TITLE></HEAD>");
out.println("<BODY>");
out.println("<TABLE BORDER>");
// Note the use of the result cache below. We can jump to
// different rows. We also take advantage of metadata
// information to retrieve data in the Java datatypes that
result.nextRow();
out.println("<TR>");
out.println("<TD>" + (String)result.getColumnValue("ID") + "</TD>");
out.println("<TD>" + (String)result.getColumnValue("NAME") + "</TD>");
out.println("<TD>" + (Integer)result.getColumnValue("DEPT") + "</TD>");
out.println("<TD>" + (BigDecimal)result.getColumnValue("COMM") + "</TD>");
out.println("</TR>");
result.previousRow();
out.println("<TR>");
out.println("<TD>" + (String)result.getColumnValue("ID") + "</TD>");
out.println("<TD>" + (String)result.getColumnValue("NAME") + "</TD>");
out.println("<TD>" + (Integer)result.getColumnValue("DEPT") + "</TD>");
out.println("<TD>" + (BigDecimal)result.getColumnValue("COMM") + "</TD>");
out.println("</TR>");
out.println("</TABLE>");
out.println("</BODY></HTML>");
out.close();
} catch (Exception e) {
System.out.println("HTML response: " + e.getMessage());
}
}
}
DA示例用了数据访问 JavaBean 来代替使用 java.sql 软件包中的类,以与数据库进行交互操作。 而CM示例使用 java.sql 软件包中的类来与数据库进行交互操作。DA 示例的优点来自于连接管理器的优化和资源管理成为可能。程序员编写 DA 示例的优点来自于数据访问 JavaBean 提供的特点和功能。DA 示例仅有一小部分区别于 CM 示例。这里只讨论区别的部分。
CM 示例 Servlet 的步骤 1,2,3,4 和 6 在 DA 示例 Servlet 中未作更改。这些步骤包括了连接管理器的设置、从缓冲池中获得连接和将连接释放回缓冲池。对CM 示例的主要更改是:
(1) metaData 变量
该变量是在代码开始的“变量”段中加以说明的,它在所有方法之外。它允许对到达的用户请求使用单个实例。变量的说明是在 init() 方法中完成的。
(2) init() 方法
已将新的代码加入 init() 方法中,以便在初次装入 Servlet 时对 metaData 对象进行一次初始化操作。新的代码从将基本查询对象 sqlQuery 创建为 String 对象开始。注意 :idParm 和 :deptParm 参数的占位符号。 sqlQuery 对象指定了 metaData 对象内的基本查询。最后,提供高级别的数据(元数据)和基本查询给 metaData 对象,这将对运行查询和使用结果有帮助。代码显示了:
addParameter() 方法注释,当运行查询时,为了便于 Servlet 进行操作,参数 idParm 将被支持为 Java Integer 数据类型, 但当运行查询时,idParm 应该转换(通过 metaData 对象)以执行重要关系数据的 SMALLINT 关系数据类型的查询。
deptParm 参数的使用相似于 addParameter() 方法,要注意相同的下属 SMALLINT 关系数据类型,第二个参数将会在 Servlet 中以不同的 Java 数据类型存在 - 如 String 类型而不是 Integer 类型。这样的话,参数可以是便于 Java 应用程序使用的 Java 数据类型,并可以自动地由 metaData 对象转换成和运行查询时所要求的关系数据类型一致的数据类型。
addColumn() 方法执行一个类似于 addParameter() 方法的功能。对于要从关系表中检索的数据的每个列, addColumn() 方法将关系数据类型映射到 Java 应用程序最便于使用的 Java 数据类型。从结果高速缓存中读取数据时和对高速缓存进行更改(然后对下属关系表进行更改)时会使用映射。
addTable() 方法明确地指定了下属关系表。如果将更改到结果高速缓存传播至下属关系表,那么就需要信息。
(3) 步骤 5
步骤 5 已经重新写入使用数据访问 JavaBean 以执行 SQL 查询来代替执行 Java.sql 软件包中的类。查询使用 selectStatement 对象运行,该对象是一个 selectStatement 数据访问 JavaBean。步骤 5 是对用户请求的响应处理的一部分。当步骤 1 至 4 已运行时,可以使用连接管理器的 dataConn 连接对象。代码显示了:
创建 dataAccessConn 对象(数据库连接 JavaBean)以建立数据访问 JavaBean 和 数据库连接(dataConn 对象)之间的链接。
创建 selectStatement 对象(选择语句 JavaBean),用于通过 dataAccessConn 对象指向数据库连接,并通过 metaData 对象指向查询。
查询是用 setParameter() 方法指定参数来“完成”的。
查询是用 execute() 方法来执行的。
结果对象(SelectResult JavaBean)是一个包含查询结果的高速缓存,它是用 getResult() 方法创建的。
数据访问 JavaBean 提供了一套丰富的用于使用结果高速缓存的特性 - 就这一点,代码显示了如何用标准 Java 代码更新结果高速缓存(和下属关系表 )中的第一行,而不需要使用 SQL 语法。
close() 方法切断了结果高速缓存和下属重要关系表间的链接,但是高速缓存中的数据仍可由 Servlet 进行本地访问。使用 close() 方法后,数据库连接就不是必需的了。步骤 6(未对 CM 示例 Servlet 作修改 )中将数据库连接释放回连接管理器缓冲池,以供另一个用户请求使用。
(4) 步骤 7
步骤 7 已完全重写(对照 CM 示例),以使用步骤 5 中检索出的查询结果高速缓存,来准备发送给用户的响应。结果高速缓存是一个选择结果数据访问 JavaBean。虽然结果高速缓存不再链接至下属关系表,但它仍可以由本地进程访问。在该步骤中,准备响应并将它发送给用户。代码显示了如下内容:
nextRow() 和 previousRow() 方法用于浏览结果高速缓存。可以使用其它浏览方法。
getColumnValue() 方法用于从结果高速缓存中检索数据。由于先前在创建 metaData 对象时的属性设置,因此可以很容易将数据造型成便于 Servlet 使用的格式。
7.2 会话编程
1.会话跟踪程序
会话是起源于同一浏览器上同一用户的一系列请求。使用该会话跟踪框架,服务器就可以保持会话的状态信息。处理会话跟踪的类是IBMSessionContextImpl(在IBM的软件包com.ibm.servlet. persona-
lization.sessiontracking中)。缺省情况下,通常由WebSphere应用服务器激活它。IBM的软件包 com.ibm.servlet.personalization.sessiontracking 包括下列3个类和1个接口:
IBMSessionData (该类扩展了com.sun.server.http.session.SessionData)
IBMSessionContextImpl (该类扩展了com.sun.server.http.session.SessionContextImpl)
IBMHttpSessionBindingEvent(该类扩展了javax.servlet.http.HttpSessionBindingEvent,并实现了Serializable,用于会话群集)
IBMHttpSessionBindingListener(扩展了HttpSessionBindingListener和Serializable,用于会话群集)当用户首次发出请求,便启用了会话跟踪程序,并创建了HttpSession对象,且将会话标识符作为一个cookie发送至浏览器。在后继请求中,浏览器将会话标识符作为一个cookie送回给用户,会话跟踪程序用它找到与该用户关联的HttpSession。当HttpSession包含用于实现IBMHttpSessionBindingListener的对象时,当实现侦听程序接口的对象与会话连接或断开连接时,会话跟踪程序会通知该对象。由于 HttpSession 对象自己是作为 IBMHttpSessionBindingEvent 的一部分进行传递的,所以 IBMHttpSessionBindingListener 接口允许您在除去会话之前,保存会话的任何部分。这与自动结束会话还是由指定的请求结束会话无关。
下面说明在集成的 Web 服务器环境中会话跟踪是如何进行工作的:
(1) 每当用 HttpServletRequest 实现的 getSession 方法获得一个会话时,拥有会话的主机在会话上加一个锁并将会话传播到所需的地方。在任何会话修改和 HttpServletRequest 实现的服务方法结束后,会话将自动发送回服务器以更新会话的副本并释放会话中所加的锁。可以将使用 HTTP 请求获得的会话作为 Servlet 的当前会话或相关联的会话,或是当前 HTTP 请求拥有的会话。
(2) 还可以用 HttpSessionContext(IBMSessionContextImpl)实现的 getSession 方法获得会话。Servlet 可以先访问 HttpSessionContext 实现的 getIds 方法,再用上下文中的 getSession 方法以访问其它几个与 HTTP 请求关联的会话。当用这种方法获得请求时,程序员必须用 IBMSessionContextImpl 的 同步化(synchronized)方法,手工地对会话解锁。同步化方法也会自动更新会话的拥有者主机的版本。
(3) 当 HttpSessionBindingListener 和 HttpSessionBindingEvent 用于集成的 Web 服务器环境时,在WebSphere应用服务器中将在会话驻留的地方激发该事件。如果会话超时,那么这个位置将是拥有者主机。如果通过调用会话对象的无效方法使会话无效,那么该位置可能是会话群集客户机或拥有者主机。如果用 removeValue() 方法除去会话对象,则将在主机上使用调用的地方激发该事件。要提高获取会话无效事件通知单的能力,请使用 IBMHttpSessionBindingListener 和 IBMHttpSessionBindingEvent,它们提供了设置和检索会话群集客户机或会话群集服务器的主机名时所需的扩展名,以接收通知单。
(4) 在将WebSphere应用服务器的一个实例配置成一个会话群集客户机后,所有其它配置参数(除了启用会话支持参数和会话交换参数)都由指定的会话群集服务器进行访问。参数设置的本地副本保留在会话群集客户机的磁盘上。当会话群集客户机将会话跟踪程序更改为另一总模式时,会话跟踪程序将使用参数设置。这种设计保证了会话群集配置的一致性。
(5) 在当前的 Java Servlet API(如 Sun Microsystems 指定的)中,HttpSession 接口的 putValue() 方法的定义对集成环境的可能性不做解释。如果添加一个不能实现会话的可串行化接口的对象,则将无法用给定的对象来传播对象。当客户机中做了会话更新时,将不能从群集客户机发送对象至群集服务器。要使应用程序在集成环境中可移动,则必须将所有对象放置在可串行化的会话中。
(6) 在会话群集环境中,添加到会话中的 Java 对象必须在每个群集主机和客户机的WebSphere应用服务器类路径中。正如前面所讨论的,这种对象将被串行化(即存储到磁盘上)。
2. 使用 HttpServletRequest 创建会话
下面结合示例(SessionSample.java),介绍使用 HttpServletRequest 创建会话的编程技术。示例的程序清单如下:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public
class SessionSample extends HttpServlet {
public void doGet (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 第一步:获取 HttpSession 对象
boolean create = true;
HttpSession session = request.getSession(create);
// 第二步:在会话中存储并检索用户定义的数据。
Integer ival = (Integer) session.getValue ("sessiontest.counter");
if (ival == null) ival = new Integer (1);
else ival = new Integer (ival.intValue () + 1);
session.putValue ("sessiontest.counter", ival);
// 第三步:输出一个包含 HttpSesson 对象的数据的 HTML 响应。
response.setContentType("text/html");
out.println("");
if(firstNameList.isEmpty()) {
out.println("<head><title>Session Tracking Test</title></head>");
out.println("<body>");
out.println("<h1>Session Tracking Test</h1>");
out.println ("You have hit this page " + ival + " times" + "<br>");
out.println ("Your " + request.getHeader("Cookie"));
out.println("</body></html>");
}
}
编程步骤如下:
(1) 获取 HttpSession 对象。
使用 HttpServletRequest 对象的 getSession() 方法来获得会话。当首次获得 HttpSession 对象时,会话跟踪程序会创建一个唯一的会话标识符并典型地将它作为 cookie 发送回浏览器。该用户(在同一浏览器上)的每个后继请求会传递 cookie 。会话跟踪程序用会话标识符来查找用户的现有 HttpSession 对象。
在代码示例的第一步中,将 Boolean 设置为真,这样如果未找到HttpSession,便会创建 HttpSession。
(2) 在会话中存储并检索用户定义的数据。
会话建立后,就可以将一个检索用户定义的数据添加到会话中。HttpSesson 对象中添加、检索和除去Java 对象的方法类似于 java.util.Dictionary 中的方法。通常用 HttpSession.putValue() 来连接对象和会话,并用 HttpSession.removeValue() 来切断对象和会话之间的连接。会话的失效也暗示着将切断当前所有会话与对象的连接。如果启用了会话持续性(缺省设置)或在会话群集环境中,请确保将要添加到会话中的 Java 对象(.class, .jar 和 .ser 文件)放置在WebSphere应用服务器类路径。
在代码示例的第二步中,Servlet 从 HttpSession 对象中读取一个整数对象,对其增量,并将之写回。可以用任何名称(例如示例中的 sessiontest.counter)来标识 HttpSession 对象中的值。因为 HttpSession 对象是由用户可访问的 Servlet 共享的,所以建议采用站点端的命名约定规则,以避免 Servlet 之间的共享冲突。
(3) 输出一个包含 HttpSesson 对象的数据的 HTML 响应(可选的)。
在代码示例的第三步中,Servlet 生成了一个 Web 页面,每次用户在会话期间访问该页面时,它就会显示 sessiontest.counter 的值。
(4) 使用 IBMHttpSessionBindingListener 接口,以在会话结束时保存数据(可选的)。
会话结束前,将通知存储在实现 IBMHttpSessionBindingListener 接口会话中的对象会话即将终止。这就允许执行后续会话处理,例如将数据保存到一个数据库中。HttpSessionBindingListener 接口提供了下列方法,必须在实现接口时实现这些方法:
valueBound();将对象连接到会话时调用该方法
valueUnbound();切断对象与会话间的连接时调用该方法
IBMHttpSessionBindingListener 接口提供了下列方法,用自己的程序来覆盖这些方法:
setReceiverHostname();设置启动事件的主机名,并支持对会话群集环境中会话失效事件的响应。
getReceiverHostname();返回接收器主机名。
(5) 结束会话
可由会话跟踪程序或指定的 Servlet 处理自动终止一个会话。
7.3 PageListSevlet技术
PageListSevlet技术是Webphere应用服务器特有的一种Servlet技术。
1.使用XML文件来配置Sevlet
Webphere应用服务器支持使用XML文件来配置Sevlet,这些配制文件称为XML Servlet 配置文件。一个XML Servlet 配置文件的示例如下:
<? xml version="1.0"? >
<servlet>
<code>SimplePageListServlet</code>
<description>Shows how to use PageListServlet class</description>
<init-parameter name="name1" value="value2"/>
<page-list>
<default-page>
<uri>/index.jsp</uri>
</default-page>
<error-page>
<uri>/error.jsp</uri>
</error-page>
<page>
<uri>/TemplateA.jsp</uri>
<page-name>page1</page-name>
</page>
<page>
<uri>/TemplateB.jsp</uri>
<page-name>page2</page-name>
</page>
</page-list>
</servlet>
页面列表(<page-list>)描述了servlet调用的那些JSP文件的抽象名,从而避免了将被调用的 JSP文件的 URL编码到servlet中。页面列表还可以包含一个缺省页面、出错页面和其它根据 HTTP 请求而调用的 JavaServer 页面。
XML Servlet 配置文件(如名为SimplePageListServlet.servlet 的XML 文档)包含如下内容:
Servlet 类文件的文件名
Servlet 说明
Servlet 初始化参数
包含 Servlet 可以调用的 JavaServer 页面的 URL(统一资源定位器)的页面列表。该页面列表
可以包括一个缺省页面、一个出错页面、一个或多个已装入的目标页面(如果它们的名称出现在 HTTP 请求中)。
该 .servlet 文件存储在WebSphere应用服务器的servlets\ 目录下,或Java 类路径目录下。当WebSphere应用服务器接收到一个对 Servlet 实例的请求时,它将在 servlet.properties 文件中查找该 Servlet 的配置信息。当使用WebSphere应用服务器的管理器配置 Servlet 初始化参数和装入选项时,管理器将把这些设置信息存储在 servlet.properties 文件中。如果WebSphere应用服务器在 servlet.properties 文件中没有找到所请求的 servlet 的配置信息,它将检查 servlets\ 目录和 Java 类路径下属于该 Servlet 实例的 .servlet 文件。当WebSphere应用服务器找到 Servlet 配置信息时,它将装入该 Servlet 实例。
在下列情况下,XML Servlet 配置文件是十分有用的:
Servlet 使用 callPage() 方法调用 JSP 时。为了调用 JSP,XML Servlet 配置文件的 page-list 元素和 PageListServlet 类(将在下一章节中讨论)消除对 URL 硬编码。如果引用的页面发生更改时,只需更新 .servlet 文件,而无需更新 Servlet 代码和重新编译该 Servlet。每当 .servlet 文件发生更改时,WebSphere应用服务器就会自动装入 Servlet 实例。
希望将一个 Servlet 的配置信息与其它的 Servlet 的配置信息分隔开时。每当使用WebSphere应用服务器的管理器来配置 Servlet 初始化参数和装入选项时,管理器将把这些设置信息存储在 servlet.properties 文件中。如果需分隔一个 Servlet(为了易于管理安全性、展开或除去应用程序)时,XML Servlet 配置支持是十分有帮助的。
WebSphere Application Studio 提供了用于生成 Servlet 的向导。这些向导实现了 XML Servlet 配置(即生成了一个 .servlet 文件)。在没有这个工具的情况下,可以创建一个扩展 PageListServlet 类的 Servlet,并使用文本编辑器创建一个 XML Servlet 配置文件,或使用 XMLServletConfig 类来为 Servlet 实例创建一个 XML Servlet 配置文件。必须注意的是,目前,使用XML Servlet 配置文件的Sevlet 必须是PageListServlet的子类或子孙类。
2.使用 PageListServlet 类
PageListServlet 包含一种 callPage() 方法,该方法调用了一个 JavaServer 页面,并将其作为对页面列表中某个页面的 HTTP 请求的响应。PageListServlet callPage() 方法接收 在XML 配置文件中的页面名、 HttpServletRequest 对象、和 HttpServletResponse 对象。与此相反,HttpServiceResponse 类的callPage() 方法通常接收一个 URL 和 HttpServletRequest 对象。
SimplePageListServlet 是一个扩展 PageListServlet 类的 Servlet 示例:
public class SimplePageListServlet extends com.ibm.servlet.PageListServlet {
public void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
try{
setRequestAttribute("testVar", "test value", req);
setRequestAttribute("otherVar", "other value", req);
String pageName = getPageNameFromRequest(req);
callPage(pageName, req, resp);
}
catch(Exception e){
handleError(req, resp, e);
}
}
}
请注意:PageListServlet在软件包com.ibm.servlet中,该类扩展了 javax.servlet.http.HttpServlet。
3.创建XML Servlet 配置文件
可以使用文本编辑器创建一个 XML Servlet 配置文件,或使用 XMLServletConfig 类来为 Servlet 实例创建一个 XML Servlet 配置文件。可以编写一个使用 XMLServletConfig 类的 Java 程序,以生成 Servlet 配置文件。 XMLServletConfig 类提供了关于设置和获取 file 元素和其内容的方法。
4.部署 Servlet 和 .servlet 文件
为了在WebSphere应用服务器上使用经编译的 Servlet 和其 XML Servlet 配置文件:
(1) 将经编译的 Servlet 和其 .servlet 文件放置在下列任一路径中:
(2) applicationserver_root\servlets,其中 applicationserver_root 是 Application Server 安装的根目录。
如果 Servlet 有软件包名称, 则请确保该 Servlet 被放置在正确的 servlets\ 子目录下。
(3) 一个在 Application Server 管理器 Java 设置屏幕上的“应用服务器Java 类路径”字段中指定的目录。
(4) 请确保在页面列表中引用的JSP 文件位于 Web 服务器的 HTML 文档目录中。
(5) 如果希望WebSphere应用服务器在启动时自动装入 Servlet 实例,请使用管理器添加 Servlet 实例名称,并指定在启动时装入。
WebSphere应用编程技术涉及Java Servlet API和WebSphere应用服务器 API,请参阅有关文档获得更详细的信息。另外,附录中的实验指导带您一起走过使用WebSphere技术开发电子商务 应用的过程。