Struts原理與實踐(3)

Struts原理與實踐(3)
作者:羅會波 發文時間:2004.08.05
line_4.jpg
marginwidth="0" marginheight="0" src="http://www.cnnet.com.cn/servlets/ad?Pool=tech_pip" frameborder="0" width="360" scrolling="no" height="300">一、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著

本文作者:羅會波 當陽市國稅局信息中心 可通過[email protected]與他聯繫
發佈了7 篇原創文章 · 獲贊 0 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章