join Department as department on employee.DepNo=department.ID (注意到條件語句我用on 沒有用where)
那么執行結果是什么呢?
id1 name1 id2 name2
++++++++++++++++++++++++++++++++++++++
001 Jplateau 01 研發部
002 Jony 01 研發部
2).left (outer) join
select employee.ID as id1,employee.Name as name1,department.ID as id2,department.Name
as name2 from Employee as employee left join Department as department on employee.DepNo=
department.ID
那么執行結果又該是什么呢?
id1 name1 id2 name2
++++++++++++++++++++++++++++++++++++++
001 Jplateau 01 研發部
002 Jony 01 研發部
003 Camel null null
{就是說此時我要已第一個表的記錄多少為準,第二個表中沒有相應紀錄的時候填充null}
3). right (outer) join
select employee.ID as id1,employee.Name as name1,department.ID as id2,department.Name
as name2 from Employee as employee right join Department as department on employee.DepNo=
department.ID
那么執行結果又該是什么呢?
id1 name1 id2 name2
++++++++++++++++++++++++++++++++++++++
001 Jplateau 01 研發部
002 Jony 01 研發部
null null 02 營銷部
{就是說此時我要已第二個表的記錄多少為準,第一個表中沒有相應紀錄的時候填充null}
4。select語句
就是要確定你要從查詢中返回哪些對象或者哪些對象的屬性。寫幾個例子吧:
select employee form Employee as employee
select employee form Employee as employee where employee.Name like 'J%'
select employee.Name form Employee as employee where employee.Name like 'J%'
select employee.ID as id1,employee.Name as name1,department.ID as id2,department.Name
as name2 from Employee as employee right join Department as department on employee.DepNo=
department.ID
select elements(employee.Name) from Employee as employee
(不明白elements到底是做什么用的?望給于說明)
等等
5。數學函數
JDO目前好像還不支持此類特性。
avg(...), sum(...), min(...), max(...)
count(*)
count(...), count(distinct ...), count(all...)
其用法和SQL基本相同
select distinct employee.name from Employee as employee
select count(distinct employee.name),count(employee) from Employee as employee
6。polymorphism (暫時不知道如何解釋?)
from com.test.Animal as animal
不光得到所有Animal得實例,而且可以得到所有Animal的子類(如果我們定義了一個子類Cat)
一個比較極端的例子
from java.lang.Object as o
可以得到所有持久類的實例
7。where語句
定義查詢語句的條件,舉幾個例子吧:
from Employee as employee where employee.Name='Jplateau'
from Employee as employee where employee.Name like 'J%'
from Employee as employee where employee.Name like '%u'
在where語句中“=”不光可以比較對象的屬性,也可以比較對象,如:
select animal from com.test.Animal as animal where animal.name=dog
8。表達式
在SQL語句中大部分的表達式在HQL中都可以使用:
mathematical operators +, -, *, /
binary comparison operators =, >=, <=, <>, !=, like
logical operations and, or, not
string concatenation ||
SQL scalar functions like upper() and lower()
Parentheses ( ) indicate grouping
in, between, is null
JDBC IN parameters ?
named parameters :name, :start_date, :x1 (這種應該是另一種"?"的變通解決方法)
SQL literals 'foo', 69, '1970-01-01 10:00:01.0'
Java public static final constants eg.Color.TABBY
其他不必解釋了,在這里我只想對查詢中的參數問題說明一下:
大家知道在SQL中進行傳遞參數進行查詢的時候,我們通常用PreparedStatement,在語句中寫一大堆的“?”,
在hql中也可以用這種方法,如:
List mates = sess.find(
"select employee.name from Employee as employee " +
"where employee.Name=? ",
name,
Hibernate.STRING
);
(說明:上面利用Session里的find方法,在hibernate的api Session中重載了很多find方法,它可以滿足你多種形式的查詢)
上邊是一個參數的情形,這種情況下緊接著引入參數和定義參數的類型,當為多個參數,調用另一個find方法,它的后兩個
參數都是數組的形式。
還有另外一種方法來解決上邊的問題,JDO也有這樣的方法,不過和hibernate的表現形式上有差別,但他們兩個骨子里卻是
一樣的,如:
Query q = sess.createQuery("select employee.name from Employee as employee where employee.Name=:name");
q.setString("name", "Jplateau");
//當有多個參數的時候在此逐一定義
Iterator employees = q.iterate();
9。order 語句
和sql語句沒什么差別,如:
select employee.name from Employee as employee where employee.Name like 'J%' order by employee.ID desc (或者asc)
10。group by 語句
同樣和sql語句沒什么差別,如:
select employee.name,employee.DepNo from Employee as employee group by employee.DepNo
select foo.id, avg( elements(foo.names) ), max( indices(foo.names) ) from eg.Foo foo group by foo.id
{Note: You may use the elements and indices constructs inside a select clause, even on databases with no subselects.}
誰幫我解釋一下上邊兩句,謝過!
11。子查詢
hibernate同樣支持子查詢,寫幾個例子:
from eg.Cat as fatcat where fatcat.weight > ( select avg(cat.weight) from eg.DomesticCat cat )
(二)條件查詢Criteria Query
。數學函數
JDO目前好像還不支持此類特性。
avg(...), sum(...), min(...), max(...)
count(*)
count(...), count(distinct ...), count(all...)
其用法和SQL基本相同
select distinct employee.name from Employee as employee
select count(distinct employee.name),count(employee) from Employee as employee
6。polymorphism (暫時不知道如何解釋?)
from com.test.Animal as animal
不光得到所有Animal得實例,而且可以得到所有Animal的子類(如果我們定義了一個子類Cat)
一個比較極端的例子
from java.lang.Object as o
可以得到所有持久類的實例
7。where語句
定義查詢語句的條件,舉幾個例子吧:
from Employee as employee where employee.Name='Jplateau'
from Employee as employee where employee.Name like 'J%'
from Employee as employee where employee.Name like '%u'
在where語句中“=”不光可以比較對象的屬性,也可以比較對象,如:
select animal from com.test.Animal as animal where animal.name=dog
8。表達式
在SQL語句中大部分的表達式在HQL中都可以使用:
mathematical operators +, -, *, /
binary comparison operators =, >=, <=, <>, !=, like
logical operations and, or, not
string concatenation ||
SQL scalar functions like upper() and lower()
Parentheses ( ) indicate grouping
in, between, is null
JDBC IN parameters ?
named parameters :name, :start_date, :x1 (這種應該是另一種"?"的變通解決方法)
SQL literals 'foo', 69, '1970-01-01 10:00:01.0'
Java public static final constants eg.Color.TABBY
其他不必解釋了,在這里我只想對查詢中的參數問題說明一下:
大家知道在SQL中進行傳遞參數進行查詢的時候,我們通常用PreparedStatement,在語句中寫一大堆的“?”,
在hql中也可以用這種方法,如:
List mates = sess.find(
"select employee.name from Employee as employee " +
"where employee.Name=? ",
name,
Hibernate.STRING
);
(說明:上面利用Session里的find方法,在hibernate的api Session中重載了很多find方法,它可以滿足你多種形式的查詢)
上邊是一個參數的情形,這種情況下緊接著引入參數和定義參數的類型,當為多個參數,調用另一個find方法,它的后兩個
參數都是數組的形式。
還有另外一種方法來解決上邊的問題,JDO也有這樣的方法,不過和hibernate的表現形式上有差別,但他們兩個骨子里卻是
一樣的,如:
Query q = sess.createQuery("select employee.name from Employee as employee where employee.Name=:name");
q.setString("name", "Jplateau");
//當有多個參數的時候在此逐一定義
Iterator employees = q.iterate();
9。order 語句
和sql語句沒什么差別,如:
select employee.name from Employee as employee where employee.Name like 'J%' order by employee.ID desc (或者asc)
10。group by 語句
同樣和sql語句沒什么差別,如:
select employee.name,employee.DepNo from Employee as employee group by employee.DepNo
select foo.id, avg( elements(foo.names) ), max( indices(foo.names) ) from eg.Foo foo group by foo.id
{Note: You may use the elements and indices constructs inside a select clause, even on databases with no subselects.}
誰幫我解釋一下上邊兩句,謝過!
11。子查詢
hibernate同樣支持子查詢,寫幾個例子:
from eg.Cat as fatcat where fatcat.weight > ( select avg(cat.weight) from eg.DomesticCat cat )
(二)條件查詢Criteria Query
為了在Struts中加載Spring context,需要在struts-config.xml文件中加入如下部分:
<struts-config>
<plug-in
className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml" />
</plug-in>
</struts-config>
第一種方法:
通過Struts的plug-in在Struts和Spring之間提供了良好的結合點。通過plug-in我們實現了Spring context的加載,不過僅僅加載Spring context并沒有什么實際的意義,還應該經過配置將Struts的Action交給Spring容器進行管理。
<action-mappings>
<action path="/login"
type="org.springframework.web.struts.DelegatingActionProxy"
name="loginForm">
<forward name="success" path="/main.jsp" />
<forward name="failure" path="/login.jsp" />
</action>
在form bean這個節點上與傳統的Struts配置沒有什么區別,而在Action上面則發生了變化。在傳統的action節點上type屬性寫入action類的完整類名,而和Spring結合后在這點上是使用了Spring提供的DelegatingActionProxy作為action的type屬性,DelegatingActionProxy同樣是org.apache.struts.action.Action的一個子類,它將把調用請求轉交給真正的Action實現。通過這樣的方式,Spring獲得了Action實例的管理權,它將對Action進行調度,并為Struts提供所需的Action實例。這樣,就可以將Action看作是Spring的一個bean,它就可以享受Spring的所有服務,如依賴注入、實例管理、事務管理等。
在applicationContext.xml中相應的配置如下的節點:
<beans>
.......
<bean name="/login" class="net.xiaxin.action.LoginAction"
singleton="false">
<property name="userDAO">
<ref bean="userDAOProxy" />
</property>
</bean>
</beans>
最后這個bean的配置是關鍵,這個名為“/login”的bean與Struts中的
<action path="/login" ……>
……
</action>節點相對應,這樣,Spring Bean Name與Struts Action Path相關聯,當Struts加載對應的Action時,DelegatingActionProxy就根據傳入的path屬性,在Spring Context尋找對應bean,并將其實例返回給Struts。與此同時,還可以看到,"/login" bean 中包含了一個userDAO 引用,Spring 在運行期將根據配置為其提供userDAO 實例,以及圍繞userDAO 的事務管理服務。這樣一來,對于Struts 開發而言,我們既可以延續Struts 的開發流程,也可以享受Spring 提供的事務管
理服務。而bean 的另外一個屬性singleton="false",指明了Action 的實例獲取方式為每次重新創建。這也解決了Struts中令人詬病的線程安全問題。
第二種方法:
為了在 struts-config.xml 文件中配置 DelegatingRequestProcessor,你需要重載 <controller> 元素的 “processorClass” 屬性。 下面的幾行應該放在 <action-mapping> 元素的后面。
<controller>
<set-property property="processorClass"
value="http://www.zhmy.com/org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>
增加這些設置之后,不管你查詢任何類型的 Action,Sping都自動在它的context配置文件中尋找。 實際上,你甚至不需要指定類型。下面兩個代碼片斷都可以工作:
<action path="/user" type="com.whatever.struts.UserAction"/>
<action path="/user"/>
如果你使用 Struts 的 modules 特性,你的 bean 命名必須含有 module 的前綴。 舉個例子,如果一個 Action 的定義為 <action path="/user"/>,而且它的 module 前綴為“admin”, 那么它應該對應名為 <bean name="/admin/user"/> 的 bean
如果第二種方法不行,再用第一種方法。
至此,SS組合已經將Struts MVC以及Spring中的Bean管理、事務管理融為一體。如
果算上userDAO 中的Hibernate 部分,我們就獲得了一個全面、成熟、高效、自頂而下的
Web 開發框架。
來源:http://deathmask1980.spaces.live.com/blog/cns!8633c46371110374!118.entry
Web層實現
1、Web層的構件和交互流程
Web層包括主要3個功能:
·上傳文件。
·列出所有已經上傳的文件列表,以供點擊下載。
·下載文件。
Web層實現構件包括與2個JSP頁面,1個ActionForm及一個Action:
·file-upload.jsp:上傳文件的頁面。
·file-list.jsp:已經上傳文件的列表頁面。
·FileActionForm:file-upload.jsp頁面表單對應的ActionForm。
·FileAction:繼承org.apache.struts.actions.DispatchAction的Action,這樣這個Action就可以通過一個URL參數區分中響應不同的請求。
Web層的這些構件的交互流程如圖 6所示:
420){this.resized=true;this.style.width=420;}" border=0 resized="true">
圖 6 Web層Struts流程圖
|
其中,在執行文件上傳的請求時,FileAction在執行文件上傳后,forward到loadAllFile出口中,loadAllFile加載數據庫中所有已經上傳的記錄,然后forward到名為fileListPage的出口中,調用file-list.jsp頁面顯示已經上傳的記錄。
2、FileAction功能
Struts 1.0的Action有一個弱項:一個Action只能處理一種請求,Struts 1.1中引入了一個DispatchAction,允許通過URL參數指定調用Action中的某個方法,如http://yourwebsite/fileAction.do?method=upload即調用FileAction中的upload方法。通過這種方式,我們就可以將一些相關的請求集中到一個Action當中編寫,而沒有必要為某個請求操作編寫一個Action類。但是參數名是要在struts-config.xml中配置的:
1. <struts-config>
2. <form-beans>
3. <form-bean name="fileActionForm" type="sshfile.web.FileActionForm" />
4. </form-beans>
5. <action-mappings>
6. <action name="fileActionForm" parameter="method" path="/fileAction"
7. type="sshfile.web.FileAction">
8. <forward name="fileListPage" path="/file-list.jsp" />
9. <forward name="loadAllFile" path="/fileAction.do?method=listAllFile" />
10. </action>
11. </action-mappings>
12. </struts-config> |
第6行的parameter="method"指定了承載方法名的參數,第9行中,我們還配置了一個調用FileAction不同方法的Action出口。
FileAction共有3個請求響應的方法,它們分別是:
·upload(…):處理上傳文件的請求。
·listAllFile(…):處理加載數據庫表中所有記錄的請求。
·download(…):處理下載文件的請求。
下面我們分別對這3個請求處理方法進行講解。
2.1 上傳文件
上傳文件的請求處理方法非常簡單,簡之言之,就是從Spring容器中獲取業務層處理類FileService,調用其save(FileActionForm form)方法上傳文件,如下所示:
1. public class FileAction
2. extends DispatchAction
3. {
4. //將上傳文件保存到數據庫中
5. public ActionForward upload(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. {
9. FileActionForm fileForm = (FileActionForm) form;
10. FileService fileService = getFileService();
11. fileService.save(fileForm);
12. return mapping.findForward("loadAllFile");
13. }
14. //從Spring容器中獲取FileService對象
15. private FileService getFileService()
16. {
17. ApplicationContext appContext = WebApplicationContextUtils.
18. getWebApplicationContext(this.getServlet().getServletContext());
19. return (FileService) appContext.getBean("fileService");
20. }
21. …
22. } |
由于FileAction其它兩個請求處理方法也需要從Spring容器中獲取FileService實例,所以我們特別提供了一個getFileService()方法(第15~21行)。重構的一條原則就是:"發現代碼中有重復的表達式,將其提取為一個變量;發現類中有重復的代碼段,將其提取為一個方法;發現不同類中有相同的方法,將其提取為一個類"。在真實的系統中,往往擁有多個Action和多個Service類,這時一個比較好的設置思路是,提供一個獲取所有Service實現對象的工具類,這樣就可以將Spring 的Service配置信息屏蔽在一個類中,否則Service的配置名字散落在程序各處,維護性是很差的。
2.2 列出所有已經上傳的文件
listAllFile方法調用Servie層方法加載T_FILE表中所有記錄,并將其保存在Request域中,然后forward到列表頁面中:
1. public class FileAction
2. extends DispatchAction
3. {
4. …
5. public ActionForward listAllFile(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. throws ModuleException
9. {
10. FileService fileService = getFileService();
11. List fileList = fileService.getAllFile();
12. request.setAttribute("fileList",fileList);
13. return mapping.findForward("fileListPage");
14. }
15. } |
file-list.jsp頁面使用Struts標簽展示出保存在Request域中的記錄:
1. <%@page contentType="text/html; charset=GBK"%>
2. <%@taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%>
3. <%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
4. <html>
5. <head>
6. <title>file-download</title>
7. </head>
8. <body bgcolor="#ffffff">
9. <ol>
10. <logic:iterate id="item" name="fileList" scope="request">
11. <li>
12. <a href='fileAction.do?method=download&fileId=
13. <bean:write name="item"property="fileId"/>'>
14. <bean:write name="item" property="fileName"/>
15. </a>
16. </li>
17. </logic:iterate>
18. </ol>
19. </body>
20. </html> |
展現頁面的每條記錄掛接著一個鏈接地址,形如:fileAction.do?method=download&fileId=xxx,method參數指定了這個請求由FileAction的download方法來響應,fileId指定了記錄的主鍵。
由于在FileActionForm中,我們定義了fileId的屬性,所以在download響應方法中,我們將可以從FileActionForm中取得fileId的值。這里涉及到一個處理多個請求Action所對應的ActionForm的設計問題,由于原來的Action只能對應一個請求,那么原來的ActionForm非常簡單,它僅需要將這個請求的參數項作為其屬性就可以了,但現在一個Action對應多個請求,每個請求所對應的參數項是不一樣的,此時的ActionForm的屬性就必須是多請求參數項的并集了。所以,除了文件上傳請求所對應的fileContent和remark屬性外還包括文件下載的fileId屬性:
420){this.resized=true;this.style.width=420;}" border=0>
圖 7 FileActionForm
|
當然這樣會造成屬性的冗余,比如在文件上傳的請求中,只會用到fileContent和remark屬性,而在文件下載的請求時,只會使用到fileId屬性。但這種冗余是會帶來好處的--它使得一個Action可以處理多個請求。
2.3 下載文件
在列表頁面中點擊一個文件下載,其請求由FileAction的download方法來響應,download方法調用業務層的FileService方法,獲取文件數據并寫出到response的響應流中。通過合理設置HTTP響應頭參數,將響應流在客戶端表現為一個下載文件對話框,其代碼如下所示:
代碼 10 業務接口實現類之download
1. public class FileAction
2. extends DispatchAction
3. {
4. …
5. public ActionForward download(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. throws ModuleException
9. {
10. FileActionForm fileForm = (FileActionForm) form;
11. FileService fileService = getFileService();
12. String fileName = fileService.getFileName(fileForm.getFileId());
13. try
14. {
15. response.setContentType("application/x-msdownload");
16. response.setHeader("Content-Disposition",
17. "attachment;" + " filename="+
18. new String(fileName.getBytes(), "ISO-8859-1"));
19. fileService.write(response.getOutputStream(), fileForm.getFileId());
20. }
21. catch (Exception e)
22. {
23. throw new ModuleException(e.getMessage());
24. }
25. return null;
26. }
27. } |
第15~18行,設置HTTP響應頭,將響應類型設置為application/x-msdownload MIME類型,則響應流在IE中將彈出一個文件下載的對話框,如圖 4所示。IE所支持的MIME類型多達26種,您可以通過這個網址查看其他的MIME類型:
http://msdn.microsoft.com/workshop/networking/moniker/overview/appendix_a.asp。
如果下載文件的文件名含有中文字符,如果不對其進行硬編碼,如第18行所示,客戶文件下載對話框中出現的文件名將會發生亂碼。
第19行代碼獲得response的輸出流,作為FileServie write(OutputStream os,String fileId)的入參,這樣文件的內容將寫到response的輸出流中。
3、web.xml文件的配置
Spring容器在何時啟動呢?我可以在Web容器初始化來執行啟動Spring容器的操作,Spring提供了兩種方式啟動的方法:
·通過org.springframework.web.context .ContextLoaderListener容器監聽器,在Web容器初始化時觸發初始化Spring容器,在web.xml中通過<listener></listener>對其進行配置。
·通過Servlet org.springframework.web.context.ContextLoaderServlet,將其配置為自動啟動的Servlet,在Web容器初始化時,通過這個Servlet啟動Spring容器。
在初始化Spring容器之前,必須先初始化log4J的引擎,Spring也提供了容器監聽器和自動啟動Servlet兩種方式對log4J引擎進行初始化:
·org.springframework.web.util .Log4jConfigListener
·org.springframework.web.util.Log4jConfigServlet
下面我們來說明如何配置web.xml啟動Spring容器:
代碼 11 web.xml中對應Spring的配置內容
1. <web-app>
2. <context-param>
3. <param-name>contextConfigLocation</param-name>
4. <param-value>/WEB-INF/applicationContext.xml</param-value>
5. </context-param>
6. <context-param>
7. <param-name>log4jConfigLocation</param-name>
8. <param-value>/WEB-INF/log4j.properties</param-value>
9. </context-param>
10. <servlet>
11. <servlet-name>log4jInitServlet</servlet-name>
12. <servlet-class>org.springframework.web.util.Log4jConfigServlet</servlet-class>
13. <load-on-startup>1</load-on-startup>
14. </servlet>
15. <servlet>
16. <servlet-name>springInitServlet</servlet-name>
17. <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
18. <load-on-startup>2</load-on-startup>
19. </servlet>
20. …
21. </web-app> |
啟動Spring容器時,需要得到兩個信息:Spring配置文件的地址和Log4J屬性文件,這兩上信息分別通過contextConfigLocationWeb和log4jConfigLocation容器參數指定,如果有多個Spring配置文件,則用逗號隔開,如:
/WEB-INF/applicationContext_1.xml, /WEB-INF/applicationContext_1.xm2
由于在啟動ContextLoaderServlet之前,必須事先初始化Log4J的引擎,所以Log4jConfigServlet必須在ContextLoaderServlet之前啟動,這通過<load-on-startup>來指定它們啟動的先后順序。
亂碼是開發Web應用程序一個比較老套又常見問題,由于不同Web應用服務器的默認編碼是不一樣的,為了方便Web應用在不同的Web應用服務器上移植,最好的做法是Web程序自身來處理編碼轉換的工作。經典的作法是在web.xml中配置一個編碼轉換過濾器,Spring就提供了一個編碼過濾器類CharacterEncodingFilter,下面,我們為應用配置上這個過濾器:
1. <web-app>
2. …
3. <filter>
4. <filter-name>encodingFilter</filter-name>
5. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
6. <init-param>
7. <param-name>encoding</param-name>
8. <param-value>GBK</param-value>
9. </init-param>
10. </filter>
11. <filter-mapping>
12. <filter-name>encodingFilter</filter-name>
13. <url-pattern>/*</url-pattern>
14. </filter-mapping>
15. …
16. </web-app> |
Spring的過濾器類是org.springframework.web.filter.CharacterEncodingFilter,通過encoding參數指定編碼轉換類型為GBK,<filter-mapping>的配置使該過濾器截獲所有的請示。
Struts的框架也需要在web.xml中配置,想必讀者朋友對Struts的配置都很熟悉,故在此不再提及,請參見本文所提供的源碼。
總結
本文通過一個文件上傳下載的Web應用,講解了如何構建基于SSH的Web應用,通過Struts和FormFile,Spring的LobHandler以及Spring為HibernateBlob處理所提供的用戶類BlobByteArrayType ,實現上傳和下載文件的功能僅需要廖廖數行的代碼即告完成。讀者只需對程序作稍許的調整,即可處理Clob字段:
·領域對象對應Clob字段的屬性聲明為String類型;
·映射文件對應Clob字段的屬性聲明為org.springframework.orm.hibernate3.support.ClobStringType類型。
本文通過SSH對文件上傳下載簡捷完美的實現得以管中窺豹了解SSH強強聯合構建Web應用的強大優勢。在行文中,還穿插了一些分層的設計經驗,配置技巧和Spring所提供的方便類,相信這些知識對您的開發都有所裨益。
作者:陳雄華出處:天極開發
轉自:http://blog.tostudy.com.cn/blog/show_3574.html
優化Web應用的性能絕不象有些人想象的那樣簡單易行,它涉及到諸多技術,從最簡單的HTML代碼修改,到復雜的EJB改造,無不涉及性能問題。但有一點是非常清楚的:要想找出和解決Web應用的性能瓶頸,就必須深入全面地了解信息在Web應用中的流程。
改善Web應用的性能不一定要局限于Web應用的Java代碼,例如有些時候,簡單地改動一下HTML頁面的質量、減少其傳輸頻度和數據量就可以有效地提高應用的性能表現;有時提高性能的關鍵卻在于修改Web應用的數據庫訪問部分——這只是Java代碼之外影響性能的兩個因素,其他還有許多因素會影響到Web應用的整體性能表現。另一方面,就Java程序本身而言,其性能優化又可以分成三個領域:基本的Java代碼優化,JSP/Servlet優化,EJB優化。
一、表現層優化
Web應用的最大性能瓶頸常常不在其他地方,而在于最基本的網絡帶寬限制。如果你的Web應用也面臨這類問題,提高性能最簡單的辦法是減少HTTP傳輸,例如用JavaScript實現客戶端編輯功能以減少數據傳輸次數,避免將數據發送到服務器端再執行合法性驗證之類的編輯操作。
應當采用一切可能措施減少通過網絡傳輸的數據。例如,你可以要求瀏覽器緩沖模塊化的JavaScript文件,在SCRIPT標記的SRC中指定:
SCRIPT LANGUAGE="JavaScript" SRC="FormChek.js"。
其他減少網絡傳輸應當注意的地方還包括:避免過度使用隱藏域,減少超長Cookie值,在RADIO、CHECKBOX和SELECT域中用代碼來替代長長的字符串,等等。不過在HTML優化方面本文不準備作全面的討論,因為WebSphere應用的開發者一般不會擔負設計表現層的責任,只要了解下面這個原理就足夠了:
性能技巧之一:盡可能減少HTTP數據傳輸的總量和頻度
二、數據庫訪問
朋友小A對Java的了解極為有限,但他卻成功地改進了許多WebSphere應用的性能。他是怎么做到的呢?原來,小A是一個數據庫專家,他通過優化數據庫訪問有效地改進了整個應用的性能,但對于Java,他只是略微了解一些有關JDBC的知識。在優化數據庫訪問時,小A做的第一件事情總是檢查數據庫的設計,有時他會建議重新構造數據庫的結構(必須指出的是,為了提高性能而重新構造數據庫結構有時可能使數據庫反規格化(De-Normalization),從而帶來維護方面的問題)。
性能技巧之二:規格化(Normalization)數據庫結構
小A做的第二件事情是執行數據庫分析,根據分析結果提出增加某個索引、減少某個索引的建議。完成這一步驟后,小A通常可以讓應用有令人滿意的性能表現,根本不必去查看應用的Java代碼。
性能技巧之三:針對常用的SQL操作建立索引,刪除多余的索引
有時,為了進一步優化應用的性能,小A會檢查Java(也許應該說是SQL)代碼,經常找到Java程序沒有合理運用PreparedStatement和連接緩沖池的情形。只要把Statement類的動態SQL替換成PreparedStatement類的靜態SQL,從連接池提取SQL連接(而不是直接創建連接),應用的性能將得到顯著的改善。注意DB2 UDB(包括其他一些數據庫)的PreparedStatement是可調整和配置的。
性能技巧之四:合理運用PreparedStatement和連接池
進一步分析應用的工作流程之后,小A有時會建議批量執行某些SQL命令,這樣就只需一個對數據庫服務器的請求就可以運行大量的SQL命令。
性能技巧之五:考慮批量執行SQL命令
既然如此,小A有時還會指出,如果應用中有些SQL命令可以組合成單個事務邏輯,那么應該可以用一個存儲過程來替代。DB2 UDB的存儲過程語言(SPL,Stored Procedure Language)非常強大,如果把數據庫操作邏輯從Web應用轉移到數據庫,一般總是對性能有益。不過需要注意的是,雖然批量執行SQL命令或使用存儲過程會提高性能,但就象重新構造數據庫結構一樣,有時會帶來維護方面的困難。
性能技巧之六:考慮使用數據庫存儲過程
檢查JDBC代碼的時候,小A總是留意對象有沒有及時正確釋放。這一點其實很重要。
性能技巧之七:及時關閉不用的Statement、ResultSet、Connection等對象(但不是在finalize方法內)
三、Java代碼
前面我們以小A的經驗為例,探討了Web應用中數據庫訪問性能的重要性。調整好數據庫之后,接下來要做的自然是深入分析應用的Java代碼。從哪里入手呢?你最好使用Java分析工具來找出性能問題的焦點所在。優化Java代碼的性能是一個艱苦的過程,因此一個重要的原則是把精力集中到那些可能引起性能問題的代碼上。換句話說,就是要尊重80/20規則:利用Java分析工具的結果,調整帶來80%性能開銷的那20%代碼。
性能技巧之八:用Java分析工具清楚地界定性能問題所在
目前市場上已經有許多優秀的Java分析工具,例如ej-technologie的JProfile(http://www.ej-technologies.com),Klgroup的Jprobe(http://www.klg.com),以及Intuitive Systems的OptimizeIt(http://www.optimizeit.com)。不過不要忘記WebSphere Studio Application Developer(WSAD)本身也集成了一個優秀的分析器,有條件的話,最好多用幾種分析工具分析Java代碼。
考慮到資金問題,你不一定樂意購買昂貴的分析軟件,但你可以用Java本身的命令行工具生成分析信息。例如,在JDK 1.3中,你可以用下面的命令將TestOrderProcessing類的CPU使用情況保存到java.hprof文件:java -Xrunhprof:cpu=times,format=a,file=java.hprof TestOrderProcessing。
這種辦法的缺點是它提供的信息條理不夠清楚,比較繁雜;也許可以找到一些源代碼開放的工具輔助分析,但一般不如使用WSAD本身的分析工具或商業化的分析工具方便。另外,如果你已經了解哪些代碼塊可能引起性能問題,可以通過保存系統時間的方式獲得分析信息,例如:
long startTime = System.currentTimeMillis();
// 執行某些操作
long endTime = System.currentTimeMillis();
3.1 基本篇
有人建議“穩定性第一,速度第二”,一般而言遵從這個建議是不會錯的,但這并不妨礙我們在編寫代碼的同時運用某些已經證實的性能技巧。例如,我們都知道String類是不可變的,連接兩個String是一項開銷很大操作。
性能技巧之九:用StringBuffer來連接兩個字符串
也許你已經注意到,SUN的許多標準Java類是線程安全的,這些類內部的同步機制實際上很容易造成性能問題。例如,Vector類就是一個線程安全的類,除非確實要用到同步機制,否則使用Vector是不值得的,如有可能,應當盡量改用非線程安全的類如ArrayList。
性能技巧之十:只有在必要時才運用線程安全的類
許多人習慣使用System.out.println來輸出跟蹤信息,但println要占用不少資源,所以輸出跟蹤信息最好使用專用日志記錄框架,如IBM的JRas或Apache的Log4j。
性能技巧之十一:用日志記錄框架類輸出跟蹤信息,而不是使用System.out.println
最后一個提高代碼性能的簡單技巧是清除類里面的調試信息,減小類的體積。IBM有一個WSAD插件,它提供了一個叫做setDebugInfo的任務,可以從Ant腳本調用。
性能技巧之十二:從正式發行的軟件中刪除調試信息
假如用戶提供了一個像http: //host/webAppPrefix/directoryName/ 這樣的包含一個目錄名但沒有包含文件名的URL,會發生什么事情呢?用戶能得到一個目錄表?一個錯誤?還是標準文件的內容?如果得到標準文件內容,是 index.html、index.jsp、default.html、default.htm或別的什么東西呢?
Welcome-file-list 元素及其輔助的welcome-file元素解決了這個模糊的問題。例如,下面的web.xml項指出,如果一個URL給出一個目錄名但未給出文件名,服務器應該首先試用index.jsp,然后再試用index.html。如果兩者都沒有找到,則結果有賴于所用的服務器(如一個目錄列表)。
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
雖然許多服務器缺省遵循這種行為,但不一定必須這樣。因此,明確地使用welcom-file-list保證可移植性是一種良好的習慣。
現在我了解到,你在開發servlet和JSP頁面時從不會犯錯誤,而且你的所有頁面是那樣的清晰,一般的程序員都不會被它們的搞糊涂。但是,是人總會犯錯誤的,用戶可能會提供不合規定的參數,使用不正確的URL或者不能提供必需的表單字段值。除此之外,其它開發人員可能不那么細心,他們應該有些工具來克服自己的不足。
error-page元素就是用來克服這些問題的。它有兩個可能的子元素,分別是:error-code和exception- type。第一個子元素error-code指出在給定的HTTP錯誤代碼出現時使用的URL。第二個子元素excpetion-type指出在出現某個給定的Java異常但未捕捉到時使用的URL。error-code和exception-type都利用location元素指出相應的URL。此 URL必須以/開始。location所指出的位置處的頁面可通過查找HttpServletRequest對象的兩個專門的屬性來訪問關于錯誤的信息,這兩個屬性分別是:javax.servlet.error.status_code和javax.servlet.error.message。
可回憶一下,在web.xml內以正確的次序聲明web-app的子元素很重要。這里只要記住,error-page出現在web.xml文件的末尾附近,servlet、servlet-name和welcome-file-list之后即可。