Skip to content

9. 利用数据源

本文旨在为在 Struts 应用程序中利用数据库奠定基础。

9.1. Struts 应用程序 /listarticles

我们希望显示记录商店所售商品特征的表中的内容。

code
商品代码 - 主键
nom
商品名称
prix
商品价格
stockActuel
商品当前库存
stockMinimum
库存低于该数值时
需启动补货

表的内容如下:

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
用于访问数据库时需实例化的类名称。该类通常位于 SGBD 发布商提供的类库(.jar)中。该类库包含用于访问数据库的 JDBC 驱动程序。 属性 driverClass 指定了该驱动程序。用于访问 SGBD 的类所包含的 .jar 文件将放置在应用程序的 WEB-INF/lib 文件夹中。
url
连接到特定数据库的连接字符串。实际上,驱动程序 JDBC 允许访问由 SGBD 管理的所有数据库。通过 url 属性,我们可以指定要使用的数据库。
user, password
数据库访问受保护。SGBD 管理通过用户名和密码进行身份验证的用户。这些信息在标签的 user 和 password 属性中指定。
minCount, maxCount
Struts 将管理与 SGBD 的连接池。建立与 SGBD 的连接是一项耗费大量时间和内存资源的操作。 与其针对应用程序收到的每个请求都单独建立并关闭与 SGBD 的连接,该应用程序会管理一个包含 n 个连接的连接池,其中 n 属于 [minCount, maxCount] 范围。如果在处理请求时,应用程序需要一个连接:
- 它将尝试从连接池中获取一个连接。如果找到一个空闲的连接,则直接使用。
- 若连接池中无空闲连接,且池内连接数少于 maxCount,则会建立一个新连接并将其放入连接池。当前请求可使用该连接。
- 如果没有空闲连接且无法建立新连接,则该请求会被挂起。
当当前请求关闭连接时,该连接并非真正关闭,而是被归还给连接池。

这里,我们有以下属性:

key
dbarticles - 这是数据源在应用程序上下文中将使用的名称
driverClass
com.mysql.jdbc.Driver。我们使用的是 MySQL 数据库。
url
jdbc:mysql://localhost/dbarticles。该数据库名为 dbarticles,位于本地机器上。
user, password
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>

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

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

9.2.3. 消息文件

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

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

# 错误
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. 视图 erreurs.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
     // listArticles(i):包含 5 个元素的数组 (String[5]) 
%>

<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>    

/liste 操作将把表 ARTICLES 的内容放入一个名为 ArrayList 的对象中,该对象在查询中以 listArticles 的名称存在。 对象 listArticles 的每个元素都是一个包含 5 个字符串的数组,这些字符串分别代表与表中某条记录相关的 5 项信息(代码、名称、价格、stockActuel、stockMinimum)。 该视图需将对象 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 的当前元素在此处称为行元素表示一个 String[5] 对象,我们通过第二个迭代对其进行遍历。 该第二次迭代的元素被称为“列”。它代表字符串数组中的当前元素,因此本身是一个字符串(代码、名称、价格、stockActuel、stockMinimum)。其值通过 <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. /liste 操作

/liste 操作旨在将一个 ArrayList 对象(该对象代表 ARTICLES 表的内容)放入请求中,以便视图进行利用。其代码如下:

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 {

         // 读取连接中“articles”表的内容
         // 在上下文初始化时建立
         // 获取数据源 dbarticles
        DataSource dataSource = this.getDataSource(request, "dbarticles");
        if (dataSource == null) {
             // 无法创建数据源
            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");
        }
         // 此处数据源已存在——正在使用它 Connection connection = null        ;
        Statement st = null;
        ResultSet rs = null;
        String requête = null;
        ArrayList alArticles = new ArrayList();
         // 处理错误
        try {
             // 获取连接
            connexion = dataSource.getConnection();
             // 准备查询 SQL
            requête =
                "select code, nom, prix, stockActuel, stockMinimum from articles order by nom";
             // 执行查询
            st = connexion.createStatement();
            rs = st.executeQuery(requête);
             // 处理结果
            while (rs.next()) {
                 // 保存当前行
                alArticles.add(
                    new String[] {
                        rs.getString("code"),
                        rs.getString("nom"),
                        rs.getString("prix"),
                        rs.getString("stockactuel"),
                        rs.getString("stockMinimum")});
                // 下一行
            } //while
             // 释放资源
            rs.close();
            st.close();
            connexion.close();
        } catch (Exception ex) {
             // 发生错误
            ActionErrors erreurs = new ActionErrors();
            erreurs.add("dbarticles",new ActionError("erreur.dbarticles", ex.getMessage()));
            this.saveErrors(request, erreurs);
            return mapping.findForward("afficherErreurs");
        }
         // 成功
        request.setAttribute("listArticles", alArticles);
        return mapping.findForward("afficherListeArticles");
    } //执行
} //类

读者应该能够理解上述代码的本质。 我们仅重点探讨唯一的新内容,即使用由 Struts 框架提供的 DataSource 对象。该 DataSource 对象代表了由配置文件中 <data-source key="dbarticles"> 部分配置的连接池。 DataSource 对象在创建后已被放入应用程序上下文中,以便该上下文中的所有对象都能访问它。因此,可通过以下方式获取该对象:

         // 正在检索dbarticles数据源
        DataSource dataSource = this.getDataSource(request, "dbarticles");

此处我们请求由键“dbarticles”标识的数据源。如果检索到指针 null,则表示该数据源在应用程序初始化时未能创建。 在这种情况下,我们将错误记录在对象 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>

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

             // 建立连接
            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>

新数据源的标识符(key)为 pgdbarticles。这一新功能源自实现 javax.sql.DataSource 接口的类。默认类是定义在 struts.jar 库中的 org.apache.struts.util.GenericDataSource。 该类将在未来的 Struts 版本中被弃用,建议在 1.1 版本中使用位于 commons-dbcp-1.1.jar 库中的 org.apache.commons.dbcp.BasicDataSource 类。该类并不一定随 Struts 软件包一起提供。 可通过网址 http://jakarta.apache.org/commons/index.html 获取,具体位于 http://jakarta.apache.org/site/binindex.cgi。 需要下载名为 Commons DBCP 的产品。 Windows用户可下载该.zip文件。该文件包含库的源代码以及相应的.jar文件。需将.jar文件从.zip压缩包中解压,并放置在应用程序的WEB-INF/lib文件夹内。同时,还需将所使用的SGBD驱动程序放置在同一文件夹中:

Image

要使用此源代码,我们需要修改 /liste 操作中的一个指令:

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

此次,我们将数据源名称设置为 pgdbarticles,即 c.a.d。该数据源为 Postgres 数据库。

接下来只需编译所有内容,启动 Tomcat,并调用 URL http://localhost:8080/listarticles/liste.do

Image

9.8. 结论

我们演示了如何使用连接池访问数据库。需要注意的是,我们并未遵循MVC架构。 实际上,/liste 动作本身会直接执行 SQL 查询来访问数据。更理想的做法是让它调用一个中间业务类,由该类隐藏从 SGBD 中检索数据这一过程。 这样,连接池很可能会由业务类来创建,也就无需在 Struts 配置文件中声明。这是一个线索:配置文件中存在 <data-sources> 部分,可能意味着我们的应用程序未遵循 MVC 架构。