Skip to content

9. 使用数据源

在此,我们的目标是为在 Struts 应用程序中操作数据库奠定基础。

9.1. Struts /listarticles 应用程序

我们希望显示一个表的内容,该表存储了商店所售商品的特征。

代码
商品代码 - 主键
名称
商品名称
价格
商品价格
当前库存
该商品当前库存
最低库存
库存低于该水平时
必须启动补货

该表格包含以下内容:

Image

listarticles 应用程序将在网页上显示相同的信息(尽管形式稍显简陋):

Image

9.2. Struts /listarticles 应用程序配置

该应用程序的 web.xml 文件采用标准格式:

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
      <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

  <taglib>
      <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
  </taglib>
  <taglib>
      <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
  </taglib>

</web-app>

struts-config.xml 文件引入了一个新的 <data-sources> 部分,允许您声明和配置数据源:

struts-config.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
    <data-sources>
        <data-source key="dbarticles">
            <set-property property="driverClass" value="com.mysql.jdbc.Driver"></set-property>
            <set-property property="url" value="jdbc:mysql://localhost/dbarticles"></set-property>
            <set-property property="user" value="admarticles"></set-property>
            <set-property property="password" value="mdparticles"></set-property>
            <set-property property="minCount" value="2"></set-property>
            <set-property property="maxCount" value="5"></set-property>
        </data-source>
    </data-sources>

    <action-mappings>
        <action path="/liste" type="istia.st.struts.articles.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/listarticles.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
    </action-mappings>

    <message-resources parameter="istia.st.struts.articles.ApplicationResources" 
        null="false" />

</struts-config>

9.2.1. 数据源

<data-sources> 部分用于声明应用程序的所有数据源,每个数据源由一个 <data-source> 部分描述。该标签支持以下几个属性:

key
在存在多个数据源时用于标识特定数据源。
driverClass
用于访问数据库的实例化类名称。该类通常位于数据库管理系统(DBMS)供应商提供的类库(.jar)中。该库包含用于数据库访问的 JDBC 驱动程序。driverClass 属性指定此驱动程序。包含 DBMS 访问类的 .jar 文件将放置在应用程序的 WEB-INF/lib 文件夹中。
url
连接到特定数据库的连接字符串。JDBC 驱动程序允许访问由数据库管理系统 (DBMS) 管理的所有数据库。通过 url 属性,我们可以指定要使用的数据库。
用户名、密码
数据库访问受保护。DBMS 管理通过用户名和密码进行身份验证的用户。这些信息在标签的 user 和 password 属性中指定。
minCount, maxCount
Struts 管理着一个连接到 DBMS 的连接池。建立与 DBMS 的连接是一项在时间和内存方面都消耗大量资源的操作。应用程序不会针对每个请求都打开和关闭与 DBMS 的连接,而是管理一个包含 n 个连接的连接池,其中 n 的取值范围在 [minCount, maxCount] 之间。如果在处理请求时,应用程序需要一个连接:
- 它将尝试从连接池中获取一个连接。若找到空闲连接,则直接使用。
- 若连接池中无空闲连接,且池内连接数少于 maxCount,则会建立新连接并将其加入池中。当前查询可使用该连接。
- 若既无空闲连接也无法创建新连接,则将该请求置于挂起状态。
当当前请求关闭连接时,该连接实际上并未被关闭,而是被归还至连接池中。

在此,我们有以下属性:

key
dbarticles - 这是数据源在应用程序上下文中将使用的名称
driverClass
com.mysql.jdbc.Driver。我们正在使用 MySQL 数据库。
url
jdbc:mysql://localhost/dbarticles。该数据库名为 dbarticles,位于本地机器上。
用户名、密码
admarticles、mdparticles。该用户已被授予 dbarticles 数据库的全部权限。
minCount, maxCount
2, 5。连接池中至少有 2 个连接,最多 5 个。

9.2.2. 操作

在 Struts 配置文件中声明的操作如下:

    <action-mappings>
        <action path="/liste" type="istia.st.struts.articles.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/listarticles.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
    </action-mappings>

唯一的操作名为 /list,并与类 istia.st.struts.articles.ListeArticlesAction 相关联。该操作最终会显示:

  • 文章页面(/vues/listarticles.jsp
  • 错误页面(/views/errors.jsp

9.2.3. 消息文件

    <message-resources parameter="istia.st.struts.articles.ApplicationResources" 
        null="false" />

ApplicationResources.properties 消息文件将放置在 WEB-INF/classes/istia/st/struts/articles 文件夹中。其内容如下:

# erreurs
errors.header=<ul>
errors.footer=</ul>
erreur.dbarticles=<li>Erreur d'accès à la base des articles ({0})</li>

可能发生的错误仅有一种:数据库访问错误。实际上,存在多种可能的错误类型,它们都归类在同一条错误信息下。不过,错误的确切原因将在 {0} 参数中指定。

9.3. 视图

9.3.1. errors.jsp 视图

该视图必须显示错误列表。我们已经多次遇到过它。

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html>
    <head>
      <title>Liste d'articles - erreurs</title>
  </head>
  <body>
      <h2>Les erreurs suivantes se sont produites</h2>
        <html:errors/>
  </body>
</html>

该视图仅使用 <html:errors/> 标签显示错误列表。

9.3.2. listarticles.jsp 视图

该视图必须显示数据库中ARTICLES表的内容。其代码如下:

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>

<%
    // listarticles : ArrayList dans la requête
    // listArticles(i) : tableau (String[5]) de 5 éléments 
%>

<html>
    <head>
        <title>DataSource Struts</title>
    </head>
    <body>
        <h3>Liste des articles</h3>
        <hr>
        <table border="1">
            <logic:iterate id="ligne" name="listArticles">
                <tr>
                    <logic:iterate id="colonne" name="ligne">
                        <td><bean:write name="colonne"/></td>
                    </logic:iterate>
                </tr>
            </logic:iterate>
        </table>
    </body>
</html>    

/list 操作将把 ARTICLES 表中的内容放入请求中的一个名为 listArticles 的 ArrayList 对象中。listArticles 对象的每个元素都是一个包含 5 个字符串的数组,代表与表中某项相关的 5 项信息(代码、名称、价格、当前库存、最低库存)。 视图必须将 listArticles 对象的内容显示在 HTML 表格中。为此,它使用 <logic:iterate> 标签:

            <logic:iterate id="ligne" name="listArticles">
                <tr>
                    <logic:iterate id="colonne" name="ligne">
                        <td><bean:write name="colonne"/></td>
                    </logic:iterate>
                </tr>
            </logic:iterate>

这里有两个迭代过程。第一个迭代遍历 ArrayList 对象 listArticles 的元素。listArticles 的当前元素在此处称为 row。 该 row 元素表示一个 String[5] 对象,该对象通过第二个迭代进行遍历。此第二个迭代的元素称为 column。它表示字符串数组的当前元素,因此是一个字符串(code、name、price、currentStock、minimumStock)。其值通过 <bean:write> 标签显示。

该视图使用了 struts-logicstruts-bean 库中的标签。因此,您必须声明对它们的使用:

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>

9.4. /list 操作

/list 操作的目的是在请求中包含一个代表 ARTICLES 表内容的 ArrayList 对象,以便视图可以使用它。其代码如下:

package istia.st.struts.articles;

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class ListeArticlesAction extends Action {

    public ActionForward execute(
        ActionMapping mapping,
        ActionForm form,
        HttpServletRequest request,
        HttpServletResponse response)
        throws IOException, ServletException {

         // reads the contents of a connection's items table
         // performed at context init
         // retrieve the dbarticles data source
        DataSource dataSource = this.getDataSource(request, "dbarticles");
        if (dataSource == null) {
             // the data source could not be created
            ActionErrors erreurs = new ActionErrors();
            erreurs.add(    "dbarticles",new ActionError("erreur.dbarticles","La source de données n'a pu être créée"));
            this.saveErrors(request, erreurs);
            return mapping.findForward("afficherErreurs");
        }
         // here the data source exists - we use it Conne        ction connexion = null;
        Statement st = null;
        ResultSet rs = null;
        String requête = null;
        ArrayList alArticles = new ArrayList();
         // error handling
        try {
             // get a connection
            connexion = dataSource.getConnection();
             // prepare query SQL
            requête =
                "select code, nom, prix, stockActuel, stockMinimum from articles order by nom";
             // execute it
            st = connexion.createStatement();
            rs = st.executeQuery(requête);
             // exploit results
            while (rs.next()) {
                 // save current line
                alArticles.add(
                    new String[] {
                        rs.getString("code"),
                        rs.getString("nom"),
                        rs.getString("prix"),
                        rs.getString("stockactuel"),
                        rs.getString("stockMinimum")});
                // next line
            } //while
             // free up resources
            rs.close();
            st.close();
            connexion.close();
        } catch (Exception ex) {
             // errors have occurred
            ActionErrors erreurs = new ActionErrors();
            erreurs.add("dbarticles",new ActionError("erreur.dbarticles", ex.getMessage()));
            this.saveErrors(request, erreurs);
            return mapping.findForward("afficherErreurs");
        }
         // it's good
        request.setAttribute("listArticles", alArticles);
        return mapping.findForward("afficherListeArticles");
    } //execute
} //class

读者应该能够理解上述代码的本质。我们将仅关注其中一个新元素:Struts 框架提供的 DataSource 对象的使用。该 DataSource 对象代表了配置文件中 <data-source key="dbarticles"> 部分所配置的连接池。创建后,DataSource 对象被放入应用程序上下文中,以便该上下文中的所有对象都能访问它。因此,其获取方式如下:

         // retrieve the dbarticles data source
        DataSource dataSource = this.getDataSource(request, "dbarticles");

在此,我们请求由键“dbarticles”标识的数据源。如果检索到空指针,则表示在应用程序初始化期间无法创建该数据源。在这种情况下,我们会将错误记录到 ActionErrors 对象中,并重定向到配置文件中由键“afficherErreurs”标识的错误页面:

        <action path="/liste" type="istia.st.struts.articles.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/listarticles.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
    </action-mappings>

获取数据源后,我们即可访问连接池。通过以下代码可建立与数据库的连接:

             // get a connection
            Connection connexion = dataSource.getConnection();

在此,连接池会为我们提供一个已复用的连接,或者如果还有可用连接,则提供一个新创建的连接。

9.5. 部署

应用程序上下文在 Tomcat 的 server.xml 配置文件中定义:

<Context path="/listarticles" reloadable="true" docBase="E:\data\serge\web\struts\articles\liste" />

应用程序的目录结构如下:

   
  
  

请注意 WEB-INF/lib 目录中的 mysql-connector-java-3.0.10-stable-bin.jar 库文件。该文件包含此处所用 MySQL 数据库的 JDBC 驱动程序。

9.6. 测试

启动 Tomcat 并访问 URL http://localhost:8080/listarticles/liste.do

Image

9.7. 第二个数据源

我们现在正在使用位于 Postgres 数据库中的第二个数据源。这里的数据同样存储在 ARTICLES 表中,且列结构与之前相同。其内容如下:

Image

该新数据源已在 Struts 配置文件中声明:

    <data-sources>
        <data-source key="dbarticles" >
            <set-property property="driverClass" value="com.mysql.jdbc.Driver"></set-property>
            <set-property property="url" value="jdbc:mysql://localhost/dbarticles"></set-property>
            <set-property property="user" value="admarticles"></set-property>
            <set-property property="password" value="mdparticles"></set-property>
            <set-property property="minCount" value="2"></set-property>
            <set-property property="maxCount" value="5"></set-property>
        </data-source>
        <data-source key="pgdbarticles" type="org.apache.commons.dbcp.BasicDataSource">
            <set-property property="driverClassName" value="org.postgresql.Driver" />
            <set-property property="url" value="jdbc:postgresql://localhost/dbarticles" />
            <set-property property="username" value="serge" />
            <set-property property="password" value="serge" />
            <set-property property="maxActive" value="10" />
            <set-property property="maxWait" value="5000" />
            <set-property property="defaultAutoCommit" value="false" />
            <set-property property="defaultReadOnly" value="false" />
        </data-source>
    </data-sources>

新数据源的标识符()为 pgdbarticles。该新功能源自实现 javax.sql.DataSource 接口的类。 默认类是 org.apache.struts.util.GenericDataSource,定义在 struts.jar 库中。该类将在未来的 Struts 版本中被弃用,对于 1.1 版本,建议使用 commons-dbcp-1.1.jar 库中的 org.apache.commons.dbcp.BasicDataSource 类。 该库并不一定包含在 Struts 软件包中。您可以在 URL http://jakarta.apache.org/commons/index.html 上找到它,具体位于 http://jakarta.apache.org/site/binindex.cgi。您必须下载名为 Commons DBCP 的产品。对于 Windows 系统,您可以下载 .zip 文件。该文件包含库的源代码以及相应的 .jar 文件。 您必须将该文件从 .zip 压缩包中解压,并将其放置在应用程序的 WEB-INF/lib 文件夹中。同时,您还必须将所用数据库管理系统(DBMS)的驱动程序放置在同一文件夹中:

Image

要使用此数据源,我们需要修改 /list 操作中的代码:

        DataSource dataSource = this.getDataSource(request, "pgdbarticles");

这次,我们请求名为 pgdbarticles 的数据源,即 Postgres 数据源。

剩下的就是编译所有内容,启动 Tomcat,并访问 URL http://localhost:8080/listarticles/liste.do

Image

9.8. 结论

我们已经演示了如何使用连接池访问数据库。请注意,此处并未遵循 MVC 架构。实际上,/list 操作本身会执行 SQL 查询来访问数据。 更理想的做法是让它调用一个中间业务类,该类将隐藏数据是从数据库管理系统中检索而来的这一事实。这样,业务类很可能会创建连接池,也就无需在 Struts 配置文件中声明它。这里有一个线索:配置文件中存在 <data-sources> 部分,可能表明我们的应用程序并未遵循 MVC 架构。