UML软件工程组织

Struts原理与实践(3)
作者:罗会波

 

一、JDBC的工作原理

Struts在本质上是java程序,要在Struts应用程序中访问数据库,首先,必须搞清楚Java Database Connectivity API(JDBC)的工作原理。正如其名字揭示的,JDBC库提供了一个底层API,用来支持独立于任何特定SQL实现的基本SQL功能。提供数据库访问的基本功能。它是将各种数据库访问的公共概念抽取出来组成的类和接口。JDBC API包括两个包:java.sql(称之为JDBC内核API)和javax.sql(称之为JDBC标准扩展)。它们合在一起,包含了用Java开发数据库应用程序所需的类。这些类或接口主要有:
Java.sql.DriverManager
Java.sql.Driver
Java.sql.Connection
Java.sql.Statement
Java.sql.PreparedStatement
Java.sql.ResultSet等

这使得从Java程序发送SQL语句到数据库变得比较容易,并且适合所有SQL方言。也就是说为一种数据库如Oracle写好了java应用程序后,没有必要再为MS SQL Server再重新写一遍。而是可以针对各种数据库系统都使用同一个java应用程序。这样表述大家可能有些难以接受,我们这里可以打一个比方:联合国开会时,联合国的成员国的与会者(相当我们这里的具体的数据库管理系统)往往都有自己的语言(方言)。大会发言人(相当于我们这里的java应用程序)不可能用各种语言来发言。你只需要使用一种语言(相当于我们这里的JDBC)来发言就行了。那么怎么保证各成员国的与会者都听懂发言呢,这就要依靠同声翻译(相当于我们这里的JDBC驱动程序)。实际上是驱动程序将java程序中的SQL语句翻译成具体的数据库能执行的语句,再交由相应的数据库管理系统去执行。因此,使用JDBC API访问数据库时,我们要针对不同的数据库采用不同的驱动程序,驱动程序实际上是适合特定的数据库JDBC接口的具体实现,它们一般具有如下三种功能:
  • 建立一个与数据源的连接
  • 发送SQL语句到数据源
  • 取回结果集

    那么,JDBC具体是如何工作的呢?

    Java.sql.DriverManager装载驱动程序,当Java.sql.DriverManager的getConnection()方法被调用时,DriverManager试图在已经注册的驱动程序中为数据库(也可以是表格化的数据源)的URL寻找一个合适的驱动程序,并将数据库的URL传到驱动程序的acceptsURL()方法中,驱动程序确认自己有连接到该URL的能力。生成的连接Connection表示与特定的数据库的会话。Statement(包括PreparedStatement和CallableStatement)对象作为在给定Connection上执行SQL语句的容器。执行完语句后生成ResultSet结果集对象,通过结果集的一系列getter就可以访问表中各列的数据。

    这里,是讲的JDBC的基本工作过程,实际应用中,往往会使用JDBC扩展对象如DataSource等,限于篇幅,就不在此详细讨论了。

    二、访问数据库所要做的基本配置

    我们以访问MS SQL Server2000数据库为例,介绍其基本的配置情况。首先,要到微软网站去下载JDBC的驱动程序,运行setup.exe将得到的三个文件:msbase.jar、mssqlserver.jar及msutil.jar放在 /webapps/mystruts/WEB-INF/lib目录下。

    在struts-config.xml文件中配置数据源

    这里,有一点要引起大家的注意的,就是,struts-config.xml中配置的各个项目是有一定的顺序要求的,几个主要项目的顺序大致是这样的:

    
    data-sources
    form-beans
    action-mappings
    message-resources
    plug-in
    


    在配置时要遵守上述顺序

    
    <data-sources>
        <data-source key="A" type="org.apache.commons.dbcp.BasicDataSource">
          <set-property property="driverClassName"
              value="com.microsoft.jdbc.sqlserver.SQLServerDriver" />
          <set-property property="url"
              value="jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=mystruts;SelectMethod=cursor" />
          <set-property property="username" value="sa" />
          <set-property property="password" value="yourpwd" />
          <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>
    


    我们来对这段配置代码做一个简单的说明:

    这句中,如果您的struts应用程序中只配置一个数据源则key="A"可以不要,而配置多个数据源时就要用这个键值区别,也就是说,可以为一个应用程序配置多个数据源让它访问多个数据库。

    
    <set-property property="url" 
            value="jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=mystruts;
            SelectMethod=cursor" />
    


    这句中的sqlserver://127.0.0.1:1433;DatabaseName=mystruts;的数据库服务器名(本例是用代表本机的ip地址)和数据库名称要与您的具体情况相同。同时,还要注意访问数据库的用户名和口令也要合乎您的实际情况。

    表示最大的活动连接数,这也说明这些连接是池化(pooling)的。

    表示对数据库的增、删、改操作必须显式地提交。即必须使用connect.commit();这样的命令才能真正让数据库表中的记录作相应的改变。设置成这样方便用户组织自己的数据库事务。

    三、现在我们就来扩展前面我们讲的那个登录的例子,让它访问存储在数据库表中的用户名和口令信息,同时也让它给出的出错信息更明确一些。

    为此,我们先要做一些准备工作,如果您还没有安装MS SQL Server2000请先安装,并下载最新的补丁包。再建一个名为mystruts的数据库,并在该数据库中建一个名为userInfo的表,该表有两个字段既:username和password,它们的字段类型都为varchar(10),其中username为主键。在该表中输入一条记录,username和password的字段值分别为lhb和awave。到此准备工作就基本做好了。

    为了访问数据库,首先,要修改Action类,修改后的代码清单如下:

    
    package action;
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import javax.servlet.http.HttpServletResponse;
    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;
    import org.apache.struts.action.ActionServlet;
    import bussness.UserInfoBo;
    import entity.UserInfoForm;
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.SQLException;
    
    public final class LogonAction extends Action {
    
      public ActionForward execute(ActionMapping mapping,
             ActionForm form,
             HttpServletRequest request,
             HttpServletResponse response)
             throws IOException, ServletException {
        UserInfoForm userInfoForm = (UserInfoForm) form;
        //从web层获得用户名和口令
        String username = userInfoForm.getUsername().trim();
        String password = userInfoForm.getPassword().trim();
        //声明错误集对象
        ActionErrors errors = new ActionErrors();
        //声明数据源和连接对象
        DataSource dataSource;
        Connection cnn=null;
    
        //校验输入
        if(username.equals("")){
          ActionError error=new ActionError("error.missing.username");
          errors.add(ActionErrors.GLOBAL_ERROR,error);
        }
        if(password.equals("")){
          ActionError error=new ActionError("error.missing.password");
          errors.add(ActionErrors.GLOBAL_ERROR,error);
        }
    
        //调用业务逻辑
        if(errors.size()==0){
          String validated = "";
          try{
            //取得数据库连接
            dataSource = getDataSource(request,"A");
            cnn = dataSource.getConnection();
    
            UserInfoBo userInfoBo=new UserInfoBo(cnn);
            validated =userInfoBo.validatePwd(username,password);
            if(validated.equals("match")){
              //一切正常就保存用户信息并转向成功的页面
              HttpSession session = request.getSession();
              session.setAttribute("userInfoForm", form);
                    return mapping.findForward("success");
            }
          }
    
          catch(Throwable e){
            //处理可能出现的错误
            e.printStackTrace();
            ActionError error=new ActionError(e.getMessage());
            errors.add(ActionErrors.GLOBAL_ERROR,error);
          }
        }
        //如出错就转向输入页面,并显示相应的错误信息
        saveErrors(request, errors);
        return new ActionForward(mapping.getInput());
      }
    }
    


    注意:dataSource = getDataSource(request,"A");这句中,如果配置中只有一个数据源,且没有key="A",则这句应写为dataSource = getDataSource(request);

    从清单上可以看出,主要就是增加了访问数据库的代码。同时,我们的业务对象的形式也发生了一个变化,原来没有参数,现在有一个代表数据库连接的参数cnn,因此我们也要对业务对象进行适当地修改。

    更改后的业务对象代码清单如下:

    
    package bussness;
    
    import entity.UserInfoForm;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.lang.Exception;
    import db.UserInfoDao;
    
    public class UserInfoBo {
      private Connection cnn=null;
    
      public UserInfoBo(Connection cnn){
        this.cnn=cnn;
      }
    
      public String validatePwd(String username,String password){
    
        String validateResult="";
       
        try{
          UserInfoDao userInfoDao = new UserInfoDao(cnn);
          validateResult=userInfoDao.validatePwd(username,password);
          if(validateResult.equals("error.logon.invalid")){
            //如果用户名与口令不匹配则报此错
            throw new RuntimeException("error.logon.invalid"); 
          }
          else if(validateResult.equals("error.removed.user")){
            //如果找不到用户则报此错,这样用户看到的出错信息会更详细
            throw new RuntimeException("error.removed.user"); 
          }
        }
        catch(Exception e){
          throw new RuntimeException(e.getMessage());
        }
        finally{
          try{
            if(cnn!=null){
              cnn.close();
            }
          }
          catch(SQLException sqle){
            sqle.printStackTrace();
            throw new RuntimeException("error.unexpected");
          }
        }
        return validateResult;
      }
    }
    


    这个业务对象的代码还是比较简单的,重点要讲的就是它在validatePwd方法中调用了一个名叫UserInfoDao的对象,它就是真正进行数据库操作的数据访问对象。其代码清单如下:

    
    package db;
    import entity.UserInfoForm;
    import java.sql.*;
    
    public class UserInfoDao {
      private Connection con;
    
      public UserInfoDao(Connection con) {
        this.con=con;
      }
      
      public String validatePwd(String username,String password){
        PreparedStatement ps=null;
        ResultSet rs=null;
        String validated="error.logon.invalid";
        UserInfoForm userInfoForm=null;
        String sql="select * from userInfo where username=?";
        try{
          if(con.isClosed()){
            throw new IllegalStateException("error.unexpected");
    
          }
          ps=con.prepareStatement(sql);
          ps.setString(1,username);
          rs=ps.executeQuery();
          if(rs.next()){
            if(!rs.getString("password").trim().equals(password)){
              return validated;//口令不正确返回口令不匹配信息
              
            }
            else{
    
              validated = "match";//口令正确返回口令匹配信息
              return validated;
            }
          }else{
            
            validated="error.removed.user";//没有找到该用户
            return validated;
            
          }
    
        }catch(SQLException e){
            e.printStackTrace();
            throw new RuntimeException("error.unexpected");
        }finally{
          try{
            if(ps!=null)
              ps.close();
            if(rs!=null)
              rs.close();
          }catch(SQLException e){
            e.printStackTrace();
            throw new RuntimeException("error.unexpected");
          }
        }
      }
    }
    


    下面,简单地分析一下数据访问对象的工作过程:

    要访问数据库,一般要经历的如下几个步骤:
  • 获得到数据库的连接
  • 创建SQL语句
  • 执行SQL语句
  • 管理结果集

    其中,得到数据库的连接本例中是在Action类中完成的,代码如下:
    dataSource = getDataSource(request,"A");
    cnn = dataSource.getConnection();

    Action在调用业务对象时将连接作为一个参数传给业务对象,再由业务对象传给数据库访问对象。

    要说明一点的是,要将struts-legacy.jar文件放在 /webapps/mystruts/WEB-INF/lib目录下。

    我们要在 /webapps/mystruts/WEB-INF/classes目录下再建一个名叫db的子目录,将数据访问类以UserInfoDao.java文件名保存在该子目录中。按照上篇文章介绍的方法,编译各个包中的.java文件。就可以启动Tomcat重新运行您的程序了。

    细心一点的读者可能都注意到了,到目前为止,我们程序中的各种消息都不是用中文表示的,在下一篇文章中,我们将讨论Struts的国际化编程即所谓的i18n编程,对我们在编程中经常遇到的乱码问题也一同作些分析。

    参考文献:
    《JSP Web 编程指南》---电子工业出版社 Jayson Falkner等著 司光亚 牛红等译
    《Java数据库编程宝典》John O'Donahue等著 甑广启 于耀等译
    《Struts in Action》Ted Husted Cedric Dumoulin George Franciscus David Winterfeldt著
    《Programming Jakarta Struts》Chuck Cavaness著
    《Mastering Jakarta Struts》James Goodwill著
    《Struts Kick Start》James Turner Kevin Bedell著
 

版权所有:UML软件工程组织