五、通过模型类操作数据库
在这一节我们来编写用于操作数据库的模型类。由于本例子是
Web
程序,因此,建议在连接数据库时使用数据库连接池。在
<Tomcat
安装目录
>"conf"Catalina"localhost
目录中打开
samples.xml
文件(如果没有该文件,则建立一个
samples.xml
文件),在
<Context>
节点中加入如下的内容:
配置连接池(用于连接数据库
struts
)
<
Resource
name
="jdbc/struts"
auth
="Container"
type
="javax.sql.DataSource"
driverClassName
="com.mysql.jdbc.Driver"
url
="jdbc:mysql://localhost:3306/struts?characterEncoding=GBK"
username
="root"
password
="1234"
maxActive
="200"
maxIdle
="50"
maxWait
="3000"
/>
本例中提供了两个可以操作数据库的模型类:Product
和SearchProduct
。其中Product
用于验证由客户端提交的产品信息,并向t_products
表中写入这些信息。而SearchProduct
类用于对t_products
表的product_name
字段进行模糊查询,并返回查询到的产品信息(包括产品ID
、产品名称和产品价格)。
由于
Product
和
SearchProduct
都需要使用数据库连接池来连接数据库,因此,可以将连接数据库的工作提出来作为一个父类
(Struts
类
)
提供,代码如下:
package
util;
import
java.sql.Connection;
public
class
Struts
{
protected
javax.naming.Context ctx
=
new
javax.naming.InitialContext();
protected
javax.sql.DataSource ds;
protected
Connection conn;
public
Struts()
throws
Exception
{
ds
=
(javax.sql.DataSource) ctx.lookup(
"
java:/comp/env/jdbc/struts
"
);
conn
=
ds.getConnection();
//
从数据库连接池获得一个Connection
}
}
在
<samples
工程目录
>"src
目录中建立一个
Product.java
文件,代码所示:
package
mystruts.model;
import
java.sql.
*
;
import
mystruts.actionform.
*
;
public
class
Product
extends
util.Struts
{
private
ProductForm form;
public
Product(ProductForm form)
throws
Exception
{
super
();
this
.form
=
form;
validate();
}
//
验证客户端提交的数据
public
void
validate()
throws
Exception
{
if
(form.getProductID().trim().equals(
""
))
throw
new
Exception(
"
产品ID不能为空!
"
);
if
(form.getProductID().length()
>
4
)
throw
new
Exception(
"
产品ID最长为4位!
"
);
if
(form.getProductName().trim().equals(
""
))
throw
new
Exception(
"
产品名称不能为空
"
);
if
(Float.compare(form.getPrice(),
0
)
<=
0
)
throw
new
Exception(
"
产品价格必须大于0
"
);
}
//
将客户端提交的产品信息保存到t_products中
public
void
save()
throws
Exception
{
try
{
String productID
=
form.getProductID();
String productName
=
form.getProductName();
float
price
=
form.getPrice();
String sql
=
"
INSERT INTO t_products VALUES('
"
+
productID
+
"
',
"
+
"
'
"
+
productName
+
"
',
"
+
String.valueOf(price)
+
"
)
"
;
PreparedStatement pstmt
=
conn.prepareStatement(sql);
pstmt.executeUpdate();
//
执行INSERT语句
pstmt.close();
conn.close();
}
catch
(Exception e)
{
throw
new
Exception(e.getMessage());
}
}
}
在Product
类中使用了一个ProductForm
类,这个类是一个ActionForm
类,它的功能是保存客户端提交的数据。关于这个类将在下面详细介绍。Product
类通过构造方法的form
参数将客户端提交的数据传入Product
类的对象实例中,并在构造方法中验证这些数据,如果发现数据不合法,就会抛出一个异常。当客户端提交的数据合法后,成功建立了一个Product
类的对象实例,然后可以通过简单地调用save
方法将数据保存到t_products
表中。
与
Product
类似,在
<samples
工程目录
>"src
目录中建立一个
SearchProduct.java
文件,代码如下:
package
mystruts.model;
import
java.sql.
*
;
import
java.util.
*
;
import
mystruts.actionform.
*
;
public
class
SearchProduct
extends
util.Struts
{
private
ProductForm form;
public
SearchProduct(ProductForm form)
throws
Exception
{
super
();
this
.form
=
form;
}
//
查询产品信息,并通过List返回查询结果
public
List
<
String[]
>
search()
throws
Exception
{
List
<
String[]
>
result
=
new
LinkedList
<
String[]
>
();
String sql
=
"
SELECT * FROM t_products WHERE product_name like '%
"
+
form.getProductName()
+
"
%'
"
;
PreparedStatement pstmt
=
conn.prepareStatement(sql);
ResultSet rs
=
pstmt.executeQuery();
//
开始执行SELECT语句
while
(rs.next())
{
String[] row
=
new
String[
3
];
row[
0
]
=
rs.getString(
1
);
row[
1
]
=
rs.getString(
2
);
row[
2
]
=
rs.getString(
3
);
result.add(row);
}
rs.close();
conn.close();
return
result;
}
}
在
SearchProduct
类也使用了
ProductForm
类,但在
SearchProduct
中并不会验证
ProductForm
对象实例中的数据,而只是将
ProductForm
对象作为传递查询请求信息(实际上只需要产品名称)的工具而已。
六、实现控制器
在这一节要实现的控制器是基于
Struts
的
Web
程序的核心部分之一:控制器实质上也是普通的
Java
类,但这个
Java
类一般要从
org.apache.struts.action.Action
类继承。控制器的主要功能是接受并处理从JSP页面提交的数据、通过模型(Model)和数据库交互以及forward到相应的页面(可以是任何页面,如html、JSP和Servlet等)。在实现控制器之前,需要先实现一个ActionForm类, 这个类的作用是保存JSP页面提交的数据。在<samples工程目录>"src目录中建立一个ProductForm.java文件,代码如下:
package
mystruts.actionform;
import
org.apache.struts.action.
*
;
public
class
ProductForm
extends
ActionForm
{
private
String productID;
//
产品ID
private
String productName;
//
产品名称
private
float
price;
//
产品价格
public
String getProductID()
{
return
productID;
}
public
void
setProductID(String productID)
{
this
.productID
=
productID;
}
public
String getProductName()
{
return
productName;
}
public
void
setProductName(String productName)
{
this
.productName
=
productName;
}
public
float
getPrice()
{
return
price;
}
public
void
setPrice(
float
price)
{
this
.price
=
price;
}
}
从上面的代码可以看出,ActionForm
类一般从
org.apache.struts.action.ActionForm
类继承,而且在类中需要按着需要保存的数据表字段添加属性。如产品ID的属性是productName。在MyEclipse中可以只定义三个private变量,然后使用MyEclipse的
【Source
】
>
【Generate Getters and Setters...
】功能自动产生getter
和setter
方法。但在给这些属性取名时要注意,private
变量的名子和数据表的字段名没有直接的关系,但必须和JSP
页面中的<html>
标签的property
属性值一致,如<html:text property="productName" />
表示输入产品名称的文本框,其中property
属性的值就是ProductForm
类中的productName
变量。如果不一致,将会抛出异常。其他和ProductForm
类的属性对应的<html>
标签可以查看上面的代码。
光有ActionForm
类还不够,还需要在struts-config.xml
中的<struts-config>
节点中添加如下的内容:
<
form-beans
>
<
form-bean
name
="saveProductForm"
type
=" mystruts.actionform.ProductForm"
/>
<
form-bean
name
="searchProductForm"
type
="mystruts.actionform.ProductForm"
/>
</
form-beans
>
上面的代码所配置的两个
ActionForm
实际上指的是同一个
ProductForm
类,但这个
ProductForm
类在后面要讲的两个动作里都要使用,为了更容易理解,为这个
ProductForm
起了两个不同的别名(
saveProductForm
和
searchProductForm
)。
下面来实现
saveProduct
动作的代码。
Struts Action
类必须一般从
org.apache.struts.action.Action
类继承。一般在
Struts Action
类需要覆盖
Action
类的
execute
方法。这个方法有每次客户端访问
Struts Action
时调用。我们可以在方法中处理客户端提交的数据,访问数据库等工作。这个方法返回一个
ActionForward
类型的值,表明在执行完
execute
后,要
forward
到的页面。描述
saveProduct
动作的类叫
SaveProductAction
。代码如下:
package
mystruts.action;
import
javax.servlet.http.
*
;
import
org.apache.struts.action.
*
;
import
mystruts.actionform.
*
;
import
mystruts.model.
*
;
public
class
SaveProductAction
extends
Action
{
//
在客户端访问saveProduct动作时执行该方法
public
ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
{
ProductForm saveProductForm
=
(ProductForm) form;
try
{
Product product
=
new
Product(saveProductForm);
product.save();
//
保存产品信息
request.setAttribute(
"
info
"
,
"
保存成功!
"
);
}
catch
(Exception e)
{
request.setAttribute(
"
info
"
, e.getMessage());
}
return
mapping.findForward(
"
save
"
);
}
}
在
SaveProductAction
类中使用了模型类
Product
验证并保存产品信息。并将操作结果信息保存在
request
的属性中,
key
为“
info
”。在
execute
的最后,使用了
ActionMapping
类的
findForward
方法在
struts-config.xml
中寻找一个叫“
save
”的
forward
。这个
forward
是一个
JSP
页,用于显示是否将产品信息保存成功的信息。为了可以在
struts-config.xml
中查找这个
forward
,需要在
struts-config.xml
的
<action-mappings>
节点中加入如下的内容。
<
action
name
="saveProductForm"
path
="/saveProduct"
scope
="request"
type
=" mystruts.action.SaveProductAction"
>
<
forward
name
="save"
path
="/mystruts/save.jsp"
/>
</
action
>
从上面的代码可以看出,那个用于显示保存状态信息的
JSP
页面叫
save.jsp
。在
<samples
工程目录
>"mystruts
目录中建立一个
save.jsp
文件,代码如下:
<
%@ page
pageEncoding
="GBK"
%
>
${requestScope.info}
在IE
中输入如下的URL
:
http://localhost:8080/samples/mystruts/newProduct.jsp
在文本框中输入相应的信息后,点“保存”按钮,如果输入的数据是合法的,就会将数据保存在t_products
中,否则会显示出错的原因。 searchProduct动作的实现和saveProduct差不多,也会为三步:实现动作类(SearchProductAction)、在struts-config.xml中添加配置信息和实现用于显示查询结果的JSP文件。下面的代码分别显示了这三步所要编写的代码。
SearchProductAction.java
package
mystruts.action;
import
javax.servlet.http.
*
;
import
org.apache.struts.action.
*
;
import
mystruts.actionform.
*
;
import
mystruts.model.
*
;
import
java.util.
*
;
public
class
SearchProductAction
extends
Action
{
public
ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
{
ProductForm searchProductForm
=
(ProductForm) form;
try
{
SearchProduct searchProduct
=
new
SearchProduct(searchProductForm);
List
<
String[]
>
result
=
searchProduct.search();
//
查询产品信息
if
(result.size()
>
0
)
//
有符合条件的产品信息
{
request.setAttribute(
"
result
"
, result);
request.setAttribute(
"
info
"
,
"
记录数:
"
+
String.valueOf(result.size()));
}
else
//
没有查到任何产品信息
request.setAttribute(
"
info
"
,
"
没有符合要求的记录!
"
);
}
catch
(Exception e)
{
request.setAttribute(
"
info
"
, e.getMessage());
}
return
mapping.findForward(
"
search
"
);
}
}
在
struts-config.xml
中配置
searchProduct
动作
<
action
name
="searchProductForm"
path
="/searchProduct"
scope
="request"
type
="mystruts.action.SearchProductAction"
>
<
forward
name
="search"
path
="/mystruts/search.jsp"
/>
</
action
>
search.jsp
<%
@ page pageEncoding
=
"
GBK
"
%>
<%
@ taglib uri
=
"
http://struts.apache.org/tags-logic
"
prefix
=
"
logic
"
%>
<%
@ taglib uri
=
"
http://java.sun.com/jsp/jstl/core
"
prefix
=
"
c
"
%>
<
html
>
<
body
>
<%
--
从request的result中取出查询结果
--
%>
<
c:set
var
="result"
value
="${requestScope.result}"
/>
<
table
width
="100%"
>
<
tr
align
="center"
>
<
td
>
${requestScope.info}
</
td
>
</
tr
>
<
tr
align
="center"
>
<
td
>
<
logic:present
name
="result"
>
<
table
border
="1"
>
<
tr
align
="center"
>
<
td
>
产品ID
</
td
>
<
td
>
产品名称
</
td
>
<
td
>
价格
</
td
>
</
tr
>
<
logic:iterate
id
="row"
name
="result"
>
<
tr
>
<
td
>
${row[0]}
</
td
>
<
td
>
${row[1]}
</
td
>
<
td
>
${row[2]}
</
td
>
</
tr
>
</
logic:iterate
>
</
table
>
</
logic:present
>
</
td
>
</
tr
>
</
table
>
</
body
>
</
html
>
在IE
中输入如下的URL
:
http://localhost:8080/samples/%20mystruts/searchProduct.jsp
在“产品名称”文本框中输入产品名称的一部分,程序就会查询出所有包含输入的产品名称的产品信息,并将结果显示出来。
七、解决
ActionForm
的乱码问题
到现在为止,程序的功能部分已经全部实现完了。但还存在一个问题。当我们在产品名称中输入中文时,虽然将客户端提交的数据成功保存到数据库中,但是在t_products
表中的product_name
字段显示的都是乱码。产生这个问题的原因只有一个,就是客户端提交的数据的编码格式和数据库的编码格式不一致造成的。当然,解决这个问题的方法有很多,但笔者认为最容易的就是使用过滤器。所谓过滤器,就是在客户端提交数据后,在交由服务端处理之前所执行的一段服务端代码(一般为Java
代码)。一个过滤器是一个实现javax.servlet.Filter
接口的类。在本例中要使用的过滤器类叫EncodingFilter
,实现代码如下:
EncodingFilter.java
package
filter;
import
java.io.IOException;
import
javax.servlet.
*
;
public
class
EncodingFilter
implements
Filter
{
public
void
destroy() { }
public
void
doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws
IOException, ServletException
{
request.setCharacterEncoding(
"
GBK
"
);
//
将客户端提交的数据设为GBK编码格式
//
继续处理客户端提交的数据,如果不写这条语句,Servlet引擎将不会处理所过滤的页面
chain.doFilter(request, response);
}
public
void
init(FilterConfig filterConfig)
throws
ServletException { }
}
Filter
接口的doFilter
方法是过滤器的核心方法。其中FilterChain
类的doFilter
方法允许继续处理客户端提交的数据。我们还可以使用这个方法来临时关闭Web
站点的某个或全部的页面(根据过滤器的设置而定)。由于本书的数据库使用的是GBK
编码格式,因此,需要使用ServletRequest
的setCharacterEncoding
方法将客户端提交的数据也设为GBK
编码格式。
除了实现过滤器类,我们还需要在web.xml
中的<web-app>
节点加入如下的配置信息才能使过滤器生效:
在
web.xml
中配置过滤器
<
filter
>
<
filter-name
>
EncodingFilter
</
filter-name
>
<
filter-class
>
filter.EncodingFilter
</
filter-class
>
</
filter
>
<
filter-mapping
>
<
filter-name
>
EncodingFilter
</
filter-name
>
<
url-pattern
>
/*
</
url-pattern
>
</
filter-mapping
>
在重新启动
Tomcat
后,重新输入一条带中文的产品信息,看看是否可以将中文保存在数据库中?