崔行臣,張明光
(山東廣播電視大學(xué)現(xiàn)代教育技術(shù)中心,山東 濟(jì)南 250014)
在Web 應(yīng)用軟件開(kāi)發(fā)中,通常要對(duì)數(shù)據(jù)進(jìn)行多種條件的查詢。在傳統(tǒng)設(shè)計(jì)中,通常是客戶一次從服務(wù)器中查詢所要的數(shù)據(jù)并顯示到客戶端,但當(dāng)符合條件的數(shù)據(jù)量較大時(shí),就會(huì)增加服務(wù)器的通信負(fù)載,減慢Web 服務(wù)器的響應(yīng)速度,用戶瀏覽數(shù)據(jù)的體驗(yàn)效果較差。因此,適當(dāng)?shù)姆猪?yè)技術(shù)是十分必要的。如果直接利用數(shù)據(jù)庫(kù)管理系統(tǒng)提供的分頁(yè)功能來(lái)對(duì)數(shù)據(jù)分頁(yè),性能比較高,但是不同的數(shù)據(jù)庫(kù)實(shí)現(xiàn)的方法各不相同,缺乏通用性。在基于JavaEE 平臺(tái)上,分頁(yè)方法傳統(tǒng)上使用在JSP 中編碼的方式來(lái)實(shí)現(xiàn),將頁(yè)面的業(yè)務(wù)操作、控制邏輯和顯示都放在JSP 中,直接在其頁(yè)面上對(duì)數(shù)據(jù)進(jìn)行操作,最后將所取得的數(shù)據(jù)在頁(yè)面進(jìn)行分頁(yè)顯示。這種方法的缺點(diǎn)是應(yīng)用系統(tǒng)框架之間高度耦合,分頁(yè)程序的重用性和移植性差[1-2]。
針對(duì)以上問(wèn)題,本文利用基于MVC 模式的Struts2 架構(gòu)來(lái)實(shí)現(xiàn)一種高效的可移植的Web 數(shù)據(jù)分頁(yè)方法,依托良好的層次機(jī)構(gòu)設(shè)計(jì)達(dá)到顯示邏輯和業(yè)務(wù)邏輯分離、代碼重用性高的目的。
圖1 Struts2 的數(shù)據(jù)流向圖Fig.1 Data flow diagram of Struts2
MVC 把一個(gè)Web 應(yīng)用分成3 個(gè)基本部分:Model (模型)、View (視圖)和Controler(控制器),這3 個(gè)部分以最小的耦合協(xié)同工作,從而提高系統(tǒng)的可擴(kuò)展性和可維護(hù)性。模型部件是軟件所處理問(wèn)題邏輯在獨(dú)立于外在顯示內(nèi)容和形式情況下的內(nèi)在抽象,它封裝了問(wèn)題的業(yè)務(wù)數(shù)據(jù)和邏輯。視圖部件把表示模型數(shù)據(jù)和邏輯關(guān)系的信息以特定形式展示出來(lái)。控制器是使模型和視圖協(xié)同工作的部件,把不同的模型和不同的視圖結(jié)合在一起,完成不同的請(qǐng)求。模型、視圖與控制器的分離,使得多個(gè)視圖共享一個(gè)模型,如果用戶通過(guò)視圖的控制器改變了模型數(shù)據(jù),所有其他依賴于這些數(shù)據(jù)的視圖都應(yīng)反映這些變化,也減少了代碼維護(hù)量。采用MVC 模式開(kāi)發(fā)有利于軟件工程化管理,每一層各司其職,提供良好的工程結(jié)構(gòu)并降低各個(gè)組件之間的耦合性。
Struts2 是實(shí)現(xiàn)MVC 的一個(gè)優(yōu)秀框架,提供了較好的層次分隔能力。Struts2 框架中使用Servlet 過(guò)濾器來(lái)作為控制器FilterDispatcher,它過(guò)濾請(qǐng)求并決定由哪個(gè)Action 來(lái)處理當(dāng)前請(qǐng)求。Action 在Struts2 中作為模型而存在,主要有兩個(gè)功能:一是進(jìn)行數(shù)據(jù)的傳遞,Action 中的成員屬性不一定封裝用戶的請(qǐng)求參數(shù),也可以封裝了Action 需要傳入下一個(gè)頁(yè)面顯示的值;二是可以用來(lái)調(diào)用業(yè)務(wù)邏輯處理請(qǐng)求,當(dāng)Action 把處理請(qǐng)求處理完畢后,會(huì)返回一個(gè)邏輯視圖。視圖表現(xiàn)形式很多,既支持JSP,也支持FreeMaker 等模版技術(shù)[3]。Struts2 的數(shù)據(jù)流向圖如圖1 所示。
Mysql、Oracle,DB2 等數(shù)據(jù)庫(kù)管理系統(tǒng)(DBMS)都提供了自身的數(shù)據(jù)庫(kù)分頁(yè)方法,而且SQL 都對(duì)其進(jìn)行了優(yōu)化處理:Oracle 使用偽數(shù)列rownum 來(lái)限制結(jié)果集的大小和起始位置;Mysql 使用limit 子句來(lái)限制返回結(jié)果集的條數(shù);Sqlserver 使用top 關(guān)鍵字可以實(shí)現(xiàn)分頁(yè)。這類(lèi)方法屬于數(shù)據(jù)層分頁(yè)技術(shù),即從數(shù)據(jù)庫(kù)查詢時(shí)就進(jìn)行過(guò)濾截取。雖然各種數(shù)據(jù)庫(kù)都提供了相應(yīng)的方法,但是在實(shí)際程序開(kāi)發(fā)中,如果數(shù)據(jù)來(lái)源由一種數(shù)據(jù)庫(kù)遷移到另外一種數(shù)據(jù)庫(kù)時(shí),由于SQL 分頁(yè)代碼不兼容,需要修改大量的程序來(lái)適應(yīng)這種變換,維護(hù)起來(lái)很困難,也不符合軟件工程開(kāi)發(fā)的思想。
將頁(yè)面的顯示、控制邏輯及業(yè)務(wù)操作都放在JSP 中,直接在其頁(yè)面進(jìn)行數(shù)據(jù)操作。這種方法雖然簡(jiǎn)潔直觀,但缺點(diǎn)是過(guò)多的業(yè)務(wù)邏輯和業(yè)務(wù)操作都混雜在顯示層頁(yè)面,不僅程序員難以維護(hù),而且應(yīng)用系統(tǒng)框架模糊、相應(yīng)組件之間緊密耦合,嚴(yán)重違背了面向?qū)ο蟪绦蛟O(shè)計(jì)原理和軟件設(shè)計(jì)模式中提倡的單一職責(zé)原則和迪米特法則。
在分頁(yè)的地方直接使用jdbc 提供的ResultSet 對(duì)象來(lái)處理數(shù)據(jù),ResultSet 可以看作是一張表,包含了符合SQL 查詢語(yǔ)句條件的所有行以及查詢的列標(biāo)題和值。ResultSet 直接在數(shù)據(jù)庫(kù)上建立游標(biāo),并維護(hù)指向其當(dāng)前數(shù)據(jù)行的光標(biāo),使游標(biāo)定位結(jié)果集。但由于游標(biāo)是放在內(nèi)存中,在整個(gè)會(huì)話期間將一直占用內(nèi)存,并不釋放數(shù)據(jù)庫(kù)連接和ResultSet 對(duì)象。這種方式在操作大型數(shù)據(jù)和訪問(wèn)用戶很多的時(shí)候,有可能導(dǎo)致DBMS 因?yàn)橘Y源耗盡而崩潰。
這種方式一般不使用,其缺陷是用戶看到的可能是過(guò)期數(shù)據(jù)并且會(huì)占用大量Web 容器內(nèi)存[4]。
將分頁(yè)查詢和顯示做成JSP taglib,簡(jiǎn)化JSP 代碼,使用簡(jiǎn)單靈活[5]。目前比較流行的分頁(yè)標(biāo)簽庫(kù)有displaytag、Pager-taglib 等。
針對(duì)以上幾種分頁(yè)方法存在的問(wèn)題,在實(shí)踐中總結(jié)出針對(duì)中小型Web 應(yīng)用系統(tǒng)比較好的分頁(yè)方法:檢索指定頁(yè)面的顯示數(shù)據(jù),用JavaBean 來(lái)封裝分頁(yè)操作和數(shù)據(jù)。每次翻頁(yè)的時(shí)候只從數(shù)據(jù)庫(kù)里檢索頁(yè)面大小的塊區(qū)的數(shù)據(jù)。這樣查詢出的記錄數(shù)很少,網(wǎng)絡(luò)傳輸數(shù)據(jù)量不大,使用連接池技術(shù)能縮短建立數(shù)據(jù)庫(kù)連接過(guò)程時(shí)間。在架構(gòu)設(shè)計(jì)上采用Struts2 框架進(jìn)行模型層、表示層和控制層的分離。各層完成獨(dú)立的功能,相互之間耦合度較小,具有易擴(kuò)展和易移植的優(yōu)點(diǎn)[6]。
圖2 分頁(yè)查詢流程框架Fig.2 Framework of paging query process
(1) 模 型 層:采 用JavaBean 來(lái)封裝頁(yè)面請(qǐng)求參數(shù)屬性,如顯示第幾頁(yè),查詢結(jié)果等。
(2)表示層:表示層的主要任務(wù),一是向業(yè)務(wù)邏輯層發(fā)出需要顯示某頁(yè)數(shù)據(jù)集的HTTP 請(qǐng)求,在該數(shù)據(jù)集請(qǐng)求中包括需要顯示的總共記錄數(shù)、共要顯示多少頁(yè)、每頁(yè)的最大數(shù)以及當(dāng)前位于第幾頁(yè)等;二是接受業(yè)務(wù)邏輯層返回的記錄結(jié)果集,并且顯示為記錄列表[4]。在本文設(shè)計(jì)的分頁(yè)模型中采用JSP 來(lái)顯示分頁(yè)數(shù)據(jù),為了增強(qiáng)顯示層的通用性和可移植性,在JSP 中使用Struts 框架的標(biāo)簽庫(kù),可以把表現(xiàn)和邏輯分離并且也使得頁(yè)面顯示功能不具體綁定在某個(gè)顯示框架下面,側(cè)重于提供HTML 表示層數(shù)據(jù)。查詢分頁(yè)數(shù)據(jù)和分頁(yè)導(dǎo)航代碼在表示層都利用Struts 標(biāo)簽顯示。
(3)控制層(Controller):采用Struts2 提供的FilterDispatcher 類(lèi)作為核心控制器,該控制器作為一個(gè)Filter運(yùn)行在Web 應(yīng)用中,負(fù)責(zé)攔截用戶請(qǐng)求后根據(jù)映射配置文件struts.xml 再把控制轉(zhuǎn)交到業(yè)務(wù)邏輯層(Business Logic)相應(yīng)的處理器。引入業(yè)務(wù)邏輯層的意義在于實(shí)現(xiàn)顯示、控制和模型的完全分離,提高擴(kuò)展性和重用性,并減輕了FilterDispatcher 的負(fù)擔(dān)。
(4)業(yè)務(wù)邏輯層:采用Struts2 中的Action 類(lèi)進(jìn)行業(yè)務(wù)組件的處理。在Action 中,分頁(yè)相關(guān)屬性被映射成普通的POJO。主要作用是處理來(lái)自表示層的請(qǐng)求,并依據(jù)該請(qǐng)求向數(shù)據(jù)層獲得當(dāng)前頁(yè)的數(shù)據(jù)記錄,并將結(jié)果集返回給表示層[7]。
(5)Dao 層:負(fù)責(zé)與數(shù)據(jù)庫(kù)的交互操作。傳遞相應(yīng)的參數(shù),根據(jù)Java 多態(tài)機(jī)制,調(diào)用相應(yīng)的數(shù)據(jù)庫(kù)管理系統(tǒng),查詢所需數(shù)據(jù)。為了充分利用已經(jīng)建立的連接,采用數(shù)據(jù)源連接池技術(shù)來(lái)提高查詢速度。
基于Struts2 框架的分頁(yè)查詢流程框架和主要類(lèi)設(shè)計(jì)如圖2 所示。
PageBean 類(lèi):作為業(yè)務(wù)模型封裝了數(shù)據(jù)庫(kù)查詢條件以及提取和保存數(shù)據(jù)的操作,記錄了記錄總數(shù)、當(dāng)前頁(yè)及每頁(yè)顯示的記錄數(shù)。
Page 類(lèi):用于產(chǎn)生分頁(yè)信息的對(duì)象,實(shí)現(xiàn)了用于顯示分頁(yè)信息的基本方法。
PagedStatement 抽象類(lèi):為了使分頁(yè)對(duì)所有數(shù)據(jù)庫(kù)具有更好的擴(kuò)展性,使用模板設(shè)計(jì)模式來(lái)抽象數(shù)據(jù)庫(kù)操作,這些操作包括執(zhí)行查詢?nèi)〉靡豁?yè)數(shù)據(jù)和數(shù)據(jù)庫(kù)管理類(lèi)進(jìn)行交互等。抽象類(lèi)PagedStatement 根據(jù)查詢語(yǔ)句和頁(yè)碼查詢出當(dāng)前頁(yè)數(shù)據(jù),返回的數(shù)據(jù)類(lèi)型沒(méi)有具體指定,可根據(jù)需要實(shí)現(xiàn)以特定方式組織數(shù)據(jù)的子類(lèi),如RowSetDataPage 類(lèi)以RowSet 類(lèi)型封裝數(shù)據(jù),ListDataPage 類(lèi)以List 集合封裝數(shù)據(jù)等等。方法定義為:public ListPage execute Query Of List Page()throws SQLException。查詢當(dāng)前頁(yè)數(shù)據(jù)時(shí),根據(jù)具體的數(shù)據(jù)庫(kù)類(lèi)型,利用Java 的多態(tài)機(jī)制來(lái)調(diào)用相應(yīng)的實(shí)現(xiàn)類(lèi)獲取數(shù)據(jù)。針對(duì)Sqlserver 數(shù)據(jù)庫(kù)的分頁(yè)查詢,實(shí)現(xiàn)類(lèi)定義為PagedStatementSQLServerImpl extends PagedStatement,如果要擴(kuò)展到 Oracle 分頁(yè)數(shù)據(jù),只需添加PagedStatement 的Oracle 實(shí)現(xiàn)類(lèi)即可。
在業(yè)務(wù)邏輯層action 中,以sqlserver 數(shù)據(jù)庫(kù)為例,調(diào)用分頁(yè)組件的分頁(yè)處理方法為:
PagedStatement pst=new PagedStatementSQLServerImpl(sql,pageno,pageNum);
ListDataPage listpage=pst.executeQueryOfListPage();
this.userlist=listpage.getList();∥當(dāng)前頁(yè)記錄數(shù)據(jù)
this.pageBar=listpage.getHTML("doQuery","pageno");∥頁(yè)面分頁(yè)代碼
返回的結(jié)果集封裝在List 集合中,封裝數(shù)據(jù)結(jié)果的類(lèi),如ListDataPage 類(lèi)有g(shù)etHTML()方法,用于生成分頁(yè)代碼,系統(tǒng)提供默認(rèn)的分頁(yè)代碼,如上下型和隨意型的分頁(yè)模型[8],如果用戶不滿意,可以編寫(xiě)自己所需的分頁(yè)代碼。生成的前臺(tái)頁(yè)面分頁(yè)導(dǎo)航條代碼封裝在pageBar 變量中,根據(jù)Struts2 機(jī)制,注入到頁(yè)面中,在表示層利用Struts2 強(qiáng)大的標(biāo)簽功能可以很方便地顯示分頁(yè)數(shù)據(jù)和分頁(yè)鏈接代碼。如果改用其它數(shù)據(jù)庫(kù),調(diào)用相應(yīng)的實(shí)現(xiàn)類(lèi)即可。
對(duì)于分頁(yè)代碼,可以放在一個(gè)單獨(dú)的文件中(page.jsp),假設(shè)原來(lái)的顯示頁(yè)面沒(méi)有分頁(yè),只要在頁(yè)面的分頁(yè)導(dǎo)航位置以 <jsp:include >方式將page.jsp 包含進(jìn)去,表示層就完成了,完全不用修改原有的代碼。因?yàn)樵诒硎緦由傻姆猪?yè)鏈接代碼中,提交表單的動(dòng)作已經(jīng)指向當(dāng)前業(yè)務(wù)邏輯Action。每次請(qǐng)求提交表單時(shí),頁(yè)碼變量pageno 被注入到相應(yīng)業(yè)務(wù)邏輯Action,根據(jù)pageno 來(lái)獲取新的一頁(yè)數(shù)據(jù)。示例代碼如下:
<form name="pageForm" method="post" action="" > <div class="pagestyle" >
<s:property escape="false" value="pageBar"/>∥生成分頁(yè)鏈接代碼
</div > </form >
以上分頁(yè)代碼經(jīng)服務(wù)器解析后,生成分頁(yè)導(dǎo)航條的HTML 代碼,包括“第幾頁(yè)”、“共幾頁(yè)”,“上一頁(yè)”、“下一頁(yè)”、“首頁(yè)”和“末頁(yè)”連接和“轉(zhuǎn)到第幾頁(yè)”的導(dǎo)航選擇列表。
以上討論的分頁(yè)模型已在某高校各部門(mén)網(wǎng)站系統(tǒng)中得到實(shí)際應(yīng)用,在構(gòu)造各級(jí)網(wǎng)站時(shí)只要涉及到數(shù)據(jù)分頁(yè)的頁(yè)面,都可以使用該方案來(lái)實(shí)現(xiàn)。操作步驟為:(1)在業(yè)務(wù)邏輯層調(diào)用接口;(2)在表示層分頁(yè)位置導(dǎo)入分頁(yè)代碼文件。測(cè)試性能比較如表1 所示。
從表1 可以看出,本文提出的解決方案主要從軟件工程的角度來(lái)改進(jìn)傳統(tǒng)的分頁(yè)查詢方式,在架構(gòu)設(shè)計(jì)、維護(hù)及用戶體驗(yàn)方面具有明顯的優(yōu)勢(shì)。在時(shí)間上,在開(kāi)發(fā)環(huán)境JDK1.6、Tomcat6.0 下,對(duì)于10 萬(wàn)條數(shù)據(jù)查詢請(qǐng)求,平均響應(yīng)時(shí)間為0.5 s,而本文提到的其它技術(shù)對(duì)10 萬(wàn)條數(shù)據(jù)記錄查詢時(shí),基于SQL 分頁(yè)方法平均響應(yīng)時(shí)間為0.6 s,JSP 分頁(yè)方法最好時(shí)間也在1 s 以上。
表1 本文模型與傳統(tǒng)方式的性能比較Table 1 Performance comparison of the presented model and the traditional model
經(jīng)測(cè)試證明,本文提出的基于MVC 思想和Struts2 架構(gòu)設(shè)計(jì)的數(shù)據(jù)分頁(yè)模型具有良好的可擴(kuò)展性、可維護(hù)性及優(yōu)秀的解耦性,獲得了較高的用戶滿意度。該方案存在的問(wèn)題在于框架模型管理不方便,下一步將研究采用Spring 框架來(lái)集成管理該模型。此外,對(duì)于頻繁讀取的數(shù)據(jù)(如:相鄰上下頁(yè)),為節(jié)省時(shí)間,可以只訪問(wèn)一次底層數(shù)據(jù)庫(kù)而把符合條件的數(shù)據(jù)全部讀取至Web 服務(wù)器,并在其上生成一個(gè)XML 文件,以后客戶端直接和XML 文件交互[9],這也是今后研究的方向。
[1]曹晉,胡谷雨.基于jsp 技術(shù)的數(shù)據(jù)庫(kù)查詢分頁(yè)顯示[J].計(jì)算機(jī)技術(shù)與發(fā)展,2007,17(5):225 -227.
[2]黃櫟橋,陸鑫.基于Struts 框架的Web 數(shù)據(jù)庫(kù)分頁(yè)技術(shù)[J].計(jì)算機(jī)工程,2008,28(6):298 -301.
[2]李剛.Struts2.1 權(quán)威指南——基于webwork 核心的MVC 開(kāi)發(fā)[M].北京:電子工業(yè)出版社,2009.
[3]劉啟文,周大海,夏秀峰.Web 應(yīng)用中可擴(kuò)展分頁(yè)技術(shù)的研究與實(shí)現(xiàn)[J].計(jì)算機(jī)應(yīng)用,2006,26(12):179 -181.
[4]何玲娟,蟻 龍,劉連臣.一種松耦合高復(fù)用MVC 模式的Web 分頁(yè)實(shí)現(xiàn)[J].計(jì)算機(jī)工程與應(yīng)用,2007,43(15):95 -97.
[5]吳東慶,張新猛,王前,等.基于SSH 架構(gòu)的分頁(yè)查詢標(biāo)簽的研究與實(shí)現(xiàn)[J].仲愷農(nóng)業(yè)工程學(xué)院學(xué)報(bào),2010,23(1):45 -48.
[6]王瑞波.一種分頁(yè)查詢優(yōu)化方法的研究與實(shí)現(xiàn)[D].北京:北京化工大學(xué),2009.
[7]張俐.基于MVC 模式的分頁(yè)組件應(yīng)用[J].計(jì)算機(jī)工程,2011,37(21):255 -257.
[8]顧志峰,李涓子.Web 應(yīng)用程序分頁(yè)策略的研究[J].計(jì)算機(jī)工程,2005,31(21):60 -62.
[9]勾成圖,張璟,李軍懷.海量數(shù)據(jù)分頁(yè)機(jī)制在Web 信息系統(tǒng)中的應(yīng)用研究[J].2005,25(8):1926 -1929.