Struts原理與實(shí)踐(3)- -
一、JDBC的工作原理
Struts在本質(zhì)上是java程序,要在Struts應(yīng)用程序中訪問數(shù)據(jù)庫,首先,必須搞清楚Java Database Connectivity API(JDBC)的工作原理。正如其名字揭示的,JDBC庫提供了一個(gè)底層API,用來支持獨(dú)立于任何特定SQL實(shí)現(xiàn)的基本SQL功能。提供數(shù)據(jù)庫訪問的基本功能。它是將各種數(shù)據(jù)庫訪問的公共概念抽取出來組成的類和接口。JDBC API包括兩個(gè)包:java.sql(稱之為JDBC內(nèi)核API)和javax.sql(稱之為JDBC標(biāo)準(zhǔn)擴(kuò)展)。它們合在一起,包含了用Java開發(fā)數(shù)據(jù)庫應(yīng)用程序所需的類。這些類或接口主要有:
Java.sql.DriverManager
Java.sql.Driver
Java.sql.Connection
Java.sql.Statement
Java.sql.PreparedStatement
Java.sql.ResultSet等
這使得從Java程序發(fā)送SQL語句到數(shù)據(jù)庫變得比較容易,并且適合所有SQL方言。也就是說為一種數(shù)據(jù)庫如Oracle寫好了java應(yīng)用程序后,沒有必要再為MS SQL Server再重新寫一遍。而是可以針對各種數(shù)據(jù)庫系統(tǒng)都使用同一個(gè)java應(yīng)用程序。這樣表述大家可能有些難以接受,我們這里可以打一個(gè)比方:聯(lián)合國開會時(shí),聯(lián)合國的成員國的與會者(相當(dāng)我們這里的具體的數(shù)據(jù)庫管理系統(tǒng))往往都有自己的語言(方言)。大會發(fā)言人(相當(dāng)于我們這里的java應(yīng)用程序)不可能用各種語言來發(fā)言。你只需要使用一種語言(相當(dāng)于我們這里的JDBC)來發(fā)言就行了。那么怎么保證各成員國的與會者都聽懂發(fā)言呢,這就要依靠同聲翻譯(相當(dāng)于我們這里的JDBC驅(qū)動程序)。實(shí)際上是驅(qū)動程序?qū)ava程序中的SQL語句翻譯成具體的數(shù)據(jù)庫能執(zhí)行的語句,再交由相應(yīng)的數(shù)據(jù)庫管理系統(tǒng)去執(zhí)行。因此,使用JDBC API訪問數(shù)據(jù)庫時(shí),我們要針對不同的數(shù)據(jù)庫采用不同的驅(qū)動程序,驅(qū)動程序?qū)嶋H上是適合特定的數(shù)據(jù)庫JDBC接口的具體實(shí)現(xiàn),它們一般具有如下三種功能:
建立一個(gè)與數(shù)據(jù)源的連接
發(fā)送SQL語句到數(shù)據(jù)源
取回結(jié)果集
那么,JDBC具體是如何工作的呢?
Java.sql.DriverManager裝載驅(qū)動程序,當(dāng)Java.sql.DriverManager的getConnection()方法被調(diào)用時(shí),DriverManager試圖在已經(jīng)注冊的驅(qū)動程序中為數(shù)據(jù)庫(也可以是表格化的數(shù)據(jù)源)的URL尋找一個(gè)合適的驅(qū)動程序,并將數(shù)據(jù)庫的URL傳到驅(qū)動程序的acceptsURL()方法中,驅(qū)動程序確認(rèn)自己有連接到該URL的能力。生成的連接Connection表示與特定的數(shù)據(jù)庫的會話。Statement(包括PreparedStatement和CallableStatement)對象作為在給定Connection上執(zhí)行SQL語句的容器。執(zhí)行完語句后生成ResultSet結(jié)果集對象,通過結(jié)果集的一系列g(shù)etter就可以訪問表中各列的數(shù)據(jù)。
這里,是講的JDBC的基本工作過程,實(shí)際應(yīng)用中,往往會使用JDBC擴(kuò)展對象如DataSource等,限于篇幅,就不在此詳細(xì)討論了。
二、訪問數(shù)據(jù)庫所要做的基本配置
我們以訪問MS SQL Server2000數(shù)據(jù)庫為例,介紹其基本的配置情況。首先,要到微軟網(wǎng)站去下載JDBC的驅(qū)動程序,運(yùn)行setup.exe將得到的三個(gè)文件:msbase.jar、mssqlserver.jar及msutil.jar放在 /webapps/mystruts/WEB-INF/lib目錄下。
在struts-config.xml文件中配置數(shù)據(jù)源
這里,有一點(diǎn)要引起大家的注意的,就是,struts-config.xml中配置的各個(gè)項(xiàng)目是有一定的順序要求的,幾個(gè)主要項(xiàng)目的順序大致是這樣的:
data-sourcesform-beansaction-mappingsmessage-resourcesplug-in
在配置時(shí)要遵守上述順序
<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>
我們來對這段配置代碼做一個(gè)簡單的說明:
這句中,如果您的struts應(yīng)用程序中只配置一個(gè)數(shù)據(jù)源則key="A"可以不要,而配置多個(gè)數(shù)據(jù)源時(shí)就要用這個(gè)鍵值區(qū)別,也就是說,可以為一個(gè)應(yīng)用程序配置多個(gè)數(shù)據(jù)源讓它訪問多個(gè)數(shù)據(jù)庫。
<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;的數(shù)據(jù)庫服務(wù)器名(本例是用代表本機(jī)的ip地址)和數(shù)據(jù)庫名稱要與您的具體情況相同。同時(shí),還要注意訪問數(shù)據(jù)庫的用戶名和口令也要合乎您的實(shí)際情況。
表示最大的活動連接數(shù),這也說明這些連接是池化(pooling)的。
表示對數(shù)據(jù)庫的增、刪、改操作必須顯式地提交。即必須使用connect.commit();這樣的命令才能真正讓數(shù)據(jù)庫表中的記錄作相應(yīng)的改變。設(shè)置成這樣方便用戶組織自己的數(shù)據(jù)庫事務(wù)。
三、現(xiàn)在我們就來擴(kuò)展前面我們講的那個(gè)登錄的例子,讓它訪問存儲在數(shù)據(jù)庫表中的用戶名和口令信息,同時(shí)也讓它給出的出錯(cuò)信息更明確一些。
為此,我們先要做一些準(zhǔn)備工作,如果您還沒有安裝MS SQL Server2000請先安裝,并下載最新的補(bǔ)丁包。再建一個(gè)名為mystruts的數(shù)據(jù)庫,并在該數(shù)據(jù)庫中建一個(gè)名為userInfo的表,該表有兩個(gè)字段既:username和password,它們的字段類型都為varchar(10),其中username為主鍵。在該表中輸入一條記錄,username和password的字段值分別為lhb和awave。到此準(zhǔn)備工作就基本做好了。
為了訪問數(shù)據(jù)庫,首先,要修改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(); //聲明錯(cuò)誤集對象 ActionErrors errors = new ActionErrors(); //聲明數(shù)據(jù)源和連接對象 DataSource dataSource; Connection cnn=null; //校驗(yàn)輸入 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); } //調(diào)用業(yè)務(wù)邏輯 if(errors.size()==0){ String validated = ""; try{ //取得數(shù)據(jù)庫連接 dataSource = getDataSource(request,"A"); cnn = dataSource.getConnection(); UserInfoBo userInfoBo=new UserInfoBo(cnn); validated =userInfoBo.validatePwd(username,password); if(validated.equals("match")){ //一切正常就保存用戶信息并轉(zhuǎn)向成功的頁面 HttpSession session = request.getSession(); session.setAttribute("userInfoForm", form); return mapping.findForward("success"); } } catch(Throwable e){ //處理可能出現(xiàn)的錯(cuò)誤 e.printStackTrace(); ActionError error=new ActionError(e.getMessage()); errors.add(ActionErrors.GLOBAL_ERROR,error); } } //如出錯(cuò)就轉(zhuǎn)向輸入頁面,并顯示相應(yīng)的錯(cuò)誤信息 saveErrors(request, errors); return new ActionForward(mapping.getInput()); }}
注意:dataSource = getDataSource(request,"A");這句中,如果配置中只有一個(gè)數(shù)據(jù)源,且沒有key="A",則這句應(yīng)寫為dataSource = getDataSource(request);
從清單上可以看出,主要就是增加了訪問數(shù)據(jù)庫的代碼。同時(shí),我們的業(yè)務(wù)對象的形式也發(fā)生了一個(gè)變化,原來沒有參數(shù),現(xiàn)在有一個(gè)代表數(shù)據(jù)庫連接的參數(shù)cnn,因此我們也要對業(yè)務(wù)對象進(jìn)行適當(dāng)?shù)匦薷摹?div style="height:15px;">
更改后的業(yè)務(wù)對象代碼清單如下:
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")){ //如果用戶名與口令不匹配則報(bào)此錯(cuò) throw new RuntimeException("error.logon.invalid"); } else if(validateResult.equals("error.removed.user")){ //如果找不到用戶則報(bào)此錯(cuò),這樣用戶看到的出錯(cuò)信息會更詳細(xì) 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; }}
這個(gè)業(yè)務(wù)對象的代碼還是比較簡單的,重點(diǎn)要講的就是它在validatePwd方法中調(diào)用了一個(gè)名叫UserInfoDao的對象,它就是真正進(jìn)行數(shù)據(jù)庫操作的數(shù)據(jù)訪問對象。其代碼清單如下:
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"); } } }}
下面,簡單地分析一下數(shù)據(jù)訪問對象的工作過程:
要訪問數(shù)據(jù)庫,一般要經(jīng)歷的如下幾個(gè)步驟:
獲得到數(shù)據(jù)庫的連接
創(chuàng)建SQL語句
執(zhí)行SQL語句
管理結(jié)果集
其中,得到數(shù)據(jù)庫的連接本例中是在Action類中完成的,代碼如下:
dataSource = getDataSource(request,"A");
cnn = dataSource.getConnection();
Action在調(diào)用業(yè)務(wù)對象時(shí)將連接作為一個(gè)參數(shù)傳給業(yè)務(wù)對象,再由業(yè)務(wù)對象傳給數(shù)據(jù)庫訪問對象。
要說明一點(diǎn)的是,要將struts-legacy.jar文件放在 /webapps/mystruts/WEB-INF/lib目錄下。
我們要在 /webapps/mystruts/WEB-INF/classes目錄下再建一個(gè)名叫db的子目錄,將數(shù)據(jù)訪問類以UserInfoDao.java文件名保存在該子目錄中。按照上篇文章介紹的方法,編譯各個(gè)包中的.java文件。就可以啟動Tomcat重新運(yùn)行您的程序了。
細(xì)心一點(diǎn)的讀者可能都注意到了,到目前為止,我們程序中的各種消息都不是用中文表示的,在下一篇文章中,我們將討論Struts的國際化編程即所謂的i18n編程,對我們在編程中經(jīng)常遇到的亂碼問題也一同作些分析。
參考文獻(xiàn):
《JSP Web 編程指南》---電子工業(yè)出版社 Jayson Falkner等著 司光亞 牛紅等譯
《Java數(shù)據(jù)庫編程寶典》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著