|
级别: 中级
赵 雄伟, 软件工程师, IBM CSDL 郑 溪龙, 软件工程师, IBM CSDL
2006 年 7 月 05 日
Spring 框架已经作为逻辑层被广泛的用于轻量级的J2EE开发中。本文将给出一个使用Spring 框架和Struts来调用存储过程的完整例子,包括如何传入参数,如何获得传出参数,如何获得结果集以及如何获得多个结果集。
Spring 框架已经作为逻辑层被广泛的用于轻量级的J2EE开发中。Spring 框架可以很好的和Struts, WebWork, Hibernate等其他开源项目集成在一起。尤其在重构现有应用的使用,Spring 框架可以提供很多帮助。存储过程可以用来封装业务逻辑,控制用户权限,以及做日常的增删改查(CRUD)。在重构已有系统的时候,一般存储过程中的业务逻辑并不需要改变,只是调用存储过程的方式需要进行调整。Spring 框架提供了完整的存储过程封装,数据源管理的功能,使用Spring 框架,开发人员可以很容易的构建轻量级J2EE应用。本文将给出一个使用Spring 框架和Struts来调用存储过程的完整例子,包括如何传入参数,如何获得传出参数,如何获得结果集以及如何获得多个结果集。
读者定位为具有DB2和Web开发经验的开发和设计人员。
读者可以学习到如何使用Spring框架来调用存储过程,可以通过文中的代码快速构建一个使用Spring框架的Web应用。本文的例子将包括使用Apache Struts进行网页表单的提交和验证,使用Spring框架来调用具有输入输出参数和返回结果集的存储过程,结果集将通过DisplayTag来进行显示。
Spring是一个广泛应用于逻辑层的开源框架,它使构建J2EE应用程序的Java开发人员可以直达实质问题的核心,而不是在提供服务的细节上花费大量时间。您可以通过developerWorks的文章"Spring 系列: Spring 框架简介"来了解Spring框架。Spring框架中一个重要的模块就是Spring DAO,它允许开发者在不同数据访问技术间切换,而且Spring提供了统一的异常处理机制,在切换的同时不用考虑异常处理。
当前很多应用程序使用存储过程来封装业务逻辑,在重构已有应用的时候,只是更改调用存储过程的方式。在调用存储过程方面现在大多直接采用JDBC的操作处理,这种方法需要创建数据库连接、创建statement语句、关闭ResultSet、关闭statement、关闭数据库连接、处理异常等等,一方面这些代码与业务逻辑不相关,另一方面也可能会给应用引入bug。因此很多人更青睐于使用数据持久化框架。在数据持久化框架中Hibernate 倍受人们关注,它主要提供Java对象与关系数据库的映射(O/R Mapping),这样只需关心操作java对象而不必关心底层数据。在Hibernate 3.0中也提供了对存储过程查询的支持,但是要求存储过程必须返回一个结果集,并且关键的是应用要遵循O/R Mapping的设计要求来架构应用系统。Spring DAO为JDBC提供了抽象层,使得开发者更高效的使用JDBC,它提供了对存储过程的支持,通过抽象类org.springframework.jdbc.object.StoredProcedure我们可以方便的调用存储过程,支持输入参数、输出参数和多个结果集。下面将通过一个实例详细的介绍使用Spring框架来调用存储过程的方法。
实例将实现在页面表单输入客户代码custNum,然后通过存储过程查询数据库中客户信息,得到所有客户信息和所查客户信息,并将其返回结果在页面上显示。您可以下载实例代码,代码可以在Eclipse环境中运行,对照代码看这篇文章更直观。
1. 编写DB2存储过程
我们首先来看DB2的存储过程。存储过程一般被用来返回数据库中表的数据或者对表的数据进行更改。在这里我们使用存储过程从表中获得结果集。
首先我们需要建立DB2环境:
1) 把远程DB2服务器编制到本地。在这里我们远程的DB2主机地址是zxl01.cn.ibm.com,端口是50001
清单1
db2 CATALOG TCPIP NODE node01 REMOTE zxl01.cn.ibm.com SERVER 50001
|
2) 在本地建立远程DB2的别名。在这里我们使用testDB作为别名。
清单2
db2 CATALOG DB testDB AT NODE node01
|
3) 创建customer数据表
清单3
DROP TABLE DSW.CUSTOMER
@
CREATE TABLE DSW.CUSTOMER
(
CUST_NUM VARCHAR(10) NOT NULL,
CUST_NAME VARCHAR(35) NOT NULL,
ADDRESS VARCHAR(35) NOT NULL,
CITY VARCHAR(35) NOT NULL,
COUNTRY VARCHAR(3) NOT NULL,
ADD_DATE TIMESTAMP NOT NULL
)
@
ALTER TABLE DSW.CUSTOMER
ADD CONSTRAINT CUSTOMER_PK
PRIMARY KEY (CUST_NUM)
@
|
4) 创建存储过程
清单4
DROP PROCEDURE DSW.S_CUSTOMER
@
CREATE PROCEDURE DSW.S_CUSTOMER (
OUT poStatus INTEGER,
IN piCust_num VARCHAR(10)
)
LANGUAGE SQL
SPECIFIC DSW.S_CUSTOMER
RESULT SETS 2
P1: BEGIN NOT ATOMIC
-------------------------------------------------------------
-- CONDITION declaration
-------------------------------------------------------------
DECLARE sqlReset CONDITION for sqlstate '80100';
-- Generic Variables
DECLARE SQLCODE INTEGER DEFAULT 0;
DECLARE SQLSTATE CHAR(5) DEFAULT '00000';
-- Generic handler variables
DECLARE hSqlcode INTEGER DEFAULT 0;
DECLARE hSqlstate CHAR(5) DEFAULT '00000';
DECLARE v_Cnt INT;
-- Cursor for returning RS to the client
DECLARE cursor1 CURSOR WITH RETURN TO CLIENT FOR (
SELECT CUST_NUM, CUST_NAME FROM DSW.CUSTOMER
);
DECLARE cursor2 CURSOR WITH RETURN TO CLIENT FOR (
SELECT * FROM DSW.CUSTOMER
WHERE CUST_NUM = piCust_num
);
OPEN cursor1;
OPEN cursor2;
SET poStatus = 2;
RETURN poStatus;
END P1
@
|
5) 插入测试数据
清单5
INSERT INTO DSW.CUSTOMER (CUST_NUM,CUST_NAME,ADDRESS,CITY,COUNTRY)
VALUES('1','Steven','shang di','BeiJing','CHN')
@
INSERT INTO DSW.CUSTOMER (CUST_NUM,CUST_NAME,ADDRESS,CITY,COUNTRY)
VALUES('2','David','shang di','BeiJing','CHN')
@
INSERT INTO DSW.CUSTOMER (CUST_NUM,CUST_NAME,ADDRESS,CITY,COUNTRY)
VALUES('3','Tony','shang di','BeiJing','CHN')
@
INSERT INTO DSW.CUSTOMER (CUST_NUM,CUST_NAME,ADDRESS,CITY,COUNTRY)
VALUES('4','Maggie','shang di','BeiJing','CHN')
@
|
2. 创建基于Spring+Struts框架的web应用
将Spring框架集成到现有的Web应用中十分容易,在web.xml中的Listener部分增加一个Spring的ContextLoaderListener同时设置contextConfigLocation参数,指向Spring的配置文件位置。如清单6所示。在web.xml中还有对于Struts的支持,读者可以参考developerworks上的Struts专题来了解如何使用Struts。
清单 6. web.xml.
将Spring和Struts进行整合的方法有很多,developerworks上的文章"使用 Spring 更好地处理 Struts 动作"中介绍了几种方法,可以参照。
3. 使用Spring framework建立数据源
在Spring的配置文件applicationContext-service.xml中我们对使用到的Java类和数据源进行了配置。通过指定driverClassName、url、username、password等属性就可以创建数据源。在清单7中我们使用直接连接数据库的方式,我们也可以使用在应用服务器中配置的DataSource来连接数据库,只需要更改为不同的Reference就可以改变对数据库的连接方式。在这方面,Spring框架显示出了很强的灵活性。
清单7. applicationContext-service.xml.
"http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app id="WebApp"></web-app> <display-name></display-name>SpringSproc
<context-param></context-param> <param-name></param-name>contextConfigLocation <param-value></param-value> /WEB-INF/applicationContext-service.xml
<listener></listener> <listener-class></listener-class> org.springframework.web.context.ContextLoaderListener
<servlet></servlet> <servlet-name></servlet-name>action <servlet-class></servlet-class> org.apache.struts.action.ActionServlet
<load-on-startup></load-on-startup>1
<servlet-mapping></servlet-mapping> <servlet-name></servlet-name>action <url-pattern></url-pattern>*.do
<welcome-file-list></welcome-file-list> <welcome-file></welcome-file>index.html
|
在Java代码SearchAction.java中将连接实例化。这也是Spring中的核心思想之一:控制反转(Inversion of Control)。清单8中(1)是把一个在applicationContext-service.xml中配置的bean实例化成对象,(2)调用getBean方法得到基于接口SearchDAO(具体见清单9) 的实例SearchImpl bean,然后再通过(3)调用具体的方法去执行存储过程。
清单8. Code in Struts SearchAction.java.
private static ApplicationContext ctx = null;
public Object getBean(String name) { |(1)
if (ctx == null) {
ctx = WebApplicationContextUtils
.getRequiredWebApplicationContext
(servlet.getServletContext());
}
return ctx.getBean(name);
}
SearchDAO mgr = (SearchDAO) getBean("SearchImpl"); |(2)
Map map = mgr.execute(custNum); |(3)
|
清单9. Code in SearchDAO.java.
package com.springsproc.dao;
import java.util.List;
public interface SearchDAO {
public Map execute(String custNum);
}
|
4. 使用Spring framework为存储过程传入参数
用户在web页面上填入custNum,提交后经Struts的validation验证传入action的FormBean中,然后再从FormBean中得到custNum,并由清单8中(3)传入Spring framework。
5. 使用Spring framework获得存储过程的传出参数和结果集
Spring framework提供了调用存储过程的方法,下面对几个相关的类和接口做简单介绍,你也可以通过Spring API获得更多详细信息。
- org.springframework.jdbc.object.StoredProcedure 这是一个抽象类,通过declareParameter方法来声明存储过程的输入输出参数,再由execute方法来调用存储过程。
- org.springframework.jdbc.core.SqlParameter 用来代表存储过程的参数。
- org.springframework.jdbc.core.ResultSetSupportingSqlParameter SqlParameter的子类,是SqlOutParameter、SqlReturnResultSet这些支持结果集参数类的公共基类。
- org.springframework.jdbc.core.SqlOutParameter ResultSetSupportingSqlParameter的子类,用来代表存储过程的输出参数。
- org.springframework.jdbc.core.SqlReturnResultSet ResultSetSupportingSqlParameter的子类,用来代表调用存储过程的输出结果集。
- org.springframework.jdbc.core.RowMapper 这是一个接口,通常为JdbcTemplate的查询方法或者存储过程返回结果集使用。
了解了Spring为我们提供的这些关于存储过程的方法和接口后,来看看具体的实现方法。如清单10所示。SearchDAOImpl.java中有三个内部类,其中SearchCustomersProcedure来负责声明数据库连接和存储过程名(2)、输入参数(6)、输出参数(5)以及输出结果集(3)(4),DemoRowMapper和DemoRowMapper2来负责取得结果集中的数据。其中(3)创建一个名为"resultSet"的结果集,通过DemoRowMapper(7)来取得结果集中的数据,当有多个结果集的时候需要重复这个过程,如(6)(8)。最后由execute方法(1)填入输入参数并执行存储过程调用。
清单10. Code in SearchDAOImpl.java.
public Map execute(String custNum) {
SearchCustomersProcedure sp = new SearchCustomersProcedure(dataSource);
Map map = new HashMap(1);
map.put("piCust_num", custNum);
Map results = sp.execute(map); |(1)
return results;
}
private class SearchCustomersProcedure extends StoredProcedure {
SearchCustomersProcedure(DataSource dataSource) {
super(dataSource, sprocName); |(2)
declareParameter(new SqlReturnResultSet("resultSet",
new DemoRowMapper())); |(3)
declareParameter(new SqlReturnResultSet("resultSet2",
new DemoRowMapper2())); |(4)
declareParameter(new SqlOutParameter("poGenStatus", Types.INTEGER));
|(5)
declareParameter(new SqlParameter("piCust_num", Types.VARCHAR));
|(6) }
}
private class DemoRowMapper implements RowMapper { |(7)
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Customer customer = new Customer();
customer.setCustNum(rs.getString(1));
customer.setCustName(rs.getString(2));
return customer;
}
}
private class DemoRowMapper2 implements RowMapper { |(8)
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Customer customer = new Customer();
customer.setCustName(rs.getString(1));
customer.setCustNum(rs.getString(2));
customer.setCustAddress(rs.getString(3));
customer.setCustCity(rs.getString(4));
customer.setCustCountry(rs.getString(5));
return customer;
}
}
|
|
|
6. 在Web页面上显示结果集 (使用DisplayTag)
在SearchAction从SearchDAOImpl取得数据以后,将数据以attribute方式存入request中,如清单11中(1)(2)所示。在页面中使用DisplayTag将request中的数据以表格形式显示出来。可以指定列名或者按某些列排序,如清单12中(1);也可以不做任何处理,只是将数据显示出来,如清单12中(2)。可以在DisplayTag Homepage得到更多关于DisplayTag的详细信息。
清单11. Code in Struts SearchAction.java.
List list = (List)map.get("resultSet");
List list2 = (List)map.get("resultSet2");
String poGenStatus = map.get("poGenStatus").toString();
log.info("poGenStatus is:" + poGenStatus);
request.setAttribute("customerList",list); |(1)
request.setAttribute("MyCustomers",list2); |(2)
return mapping.findForward("list");
|
清单12. Code in Struts SearchByCustNumber.jsp
|(1)
<display:table id="customers" requesturi="" cellspacing="2" cellpadding="2" defaultsort="1" name="customerList">
<display:column title="Customer number" headerclass="sortable" headerstyleclass="header" property="custNum" sort="true"></display:column>
<display:column title="Customer name" headerclass="sortable" headerstyleclass="header" property="custName" sort="true"></display:column>
</display:table>
|(2)
<display:table id="myCustomer" requesturi="" cellspacing="2" cellpadding="2" defaultsort="1" name="myCustomer">
</display:table>
|
这样使用Spring framework调用存储过程的实例就完成了,运行该实例,如下图所示:
使用Spring framework的优点
Spring框架提供了一些接口和类,封装了对数据库的处理,极大地简化了对数据库的操作。开发人员可以把更多的精力放在业务逻辑上。数据库连接等信息写在配置文件中,在运行期装入bean中进行连接操作实例化的方法使系统更加灵活,可以在部署应用的时候很方便的更改,不仅提高了团队开发的效率,也提高了系统的可维护性。
总结
本文以一个实例介绍了使用Spring框架调用存储过程的方法,通过本文您已经可以使用Spring框架结合不同类型的存储过程来对DB2中的数据进行操作。并且参照这个实例您可以创建基于Struts + Spring框架的web应用系统。
|