五、通过模型类操作数据库

    在这一节我们来编写用于操作数据库的模型类。由于本例子是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"/>

本例中提供了两个可以操作数据库的模型类:ProductSearchProduct。其中Product用于验证由客户端提交的产品信息,并向t_products表中写入这些信息。而SearchProduct类用于对t_products表的product_name字段进行模糊查询,并返回查询到的产品信息(包括产品ID、产品名称和产品价格)。
   由于ProductSearchProduct都需要使用数据库连接池来连接数据库,因此,可以将连接数据库的工作提出来作为一个父类(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对象作为传递查询请求信息(实际上只需要产品名称)的工具而已。

六、实现控制器

   
在这一节要实现的控制器是基于StrutsWeb程序的核心部分之一:控制器实质上也是普通的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...】功能自动产生gettersetter方法。但在给这些属性取名时要注意,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起了两个不同的别名(saveProductFormsearchProductForm)。
   
下面来实现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编码格式,因此,需要使用ServletRequestsetCharacterEncoding方法将客户端提交的数据也设为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后,重新输入一条带中文的产品信息,看看是否可以将中文保存在数据库中?