top
Loading...
混合Eclipse、WTP、Struts和Hibernate
使用Eclipse Web Tools Project、Tomcat應用服務器和MySQL數據庫服務器,創建了一個Web應用程序。盡管該應用程序(DBTest)可能會很不錯,但是也會存在一些局限性:
  • 在servlet代碼中,Java Server Pages (JSP) 名稱是硬編碼的
  • SQL也被硬編碼到命令類中。

幸運的是,這些問題可以通過兩種有趣的解決方案得以解決。第一個問題可通過使用開源Struts框架解決,該框架通過將模型動作映射到一個簡單配置文件中的視圖組件(比如JSP),從而分離應用程序的模型、視圖和控制器。

第二個問題可使用提供Java和關系數據庫持久性的框架來解決。Hibernate框架在對象和數據庫表之間提供了一個強大的高性能映射引擎。本文將使用下列技術:
  • J2SE 5.0 JRE:http://java.sun.com/j2se
  • Eclipse 3.1:www.eclipse.org
  • WTP 1.0:www.eclipse.org/webtools
  • Tomcat 5.0:http://jakarta.apache.org/tomcat/
  • MySQL 4.0.25:www.mysql.com
  • MySQL Connector/J driver 3.1:www.mysql.com/products/connector/j/
  • Struts 1.1:http://struts.apache.org
  • Hibernate 3...www.hibernate.org
應用程序概述

我們再扼要重述一下上次我們做了些什么。該基本Web應用程序實現了下列用例:
  • 顧客必須在站點注冊以下單
  • 顧客可以下單
  • 顧客可以瀏覽自己的訂單
  • 管理員可以列出全部已注冊顧客

該系統使用通用servlet/jsp編程模型、MySQL數據庫以及Tomcat應用服務器實現。系統域模型由Customer(顧客)和Order(訂單)兩個類表示(參見圖1)。

圖1

創建了兩個對應的數據庫表CUSTOMERS和ORDERS來表示這些對象所持有的數據。還創建了4個負責執行上述用例的數據庫命令類,以及四個作為控制器的Servlet,來收集用戶輸入信息,調用這些命令,并將響應轉發給適當的JSP。CommandExecutor類負責使用Tomcat連接池處理數據庫連接。

添加Struts支持

使用File-Import選項并選擇要導入的WAR文件,將DBTestWAR文件導入Eclipse工作空間。如果工作空間中沒有DBTest項目,上述操作就可以了。如果工作空間中已經有了DBTest項目,在Navigator視圖的已有項目上右擊,然后選擇復制和粘貼,保存現有項目。當提示輸入新項目名稱時,選擇DBTestStruts作為新項目名,以便不會覆蓋現有項目。現在,為添加Struts支持,必須將下列文件復制到WEB-INFlib文件夾:struts.jar、commons-lang.jar、commons-collections.jar、commons-beanutils.jar、commons-validator.jar、commons-logging.jar、commons-digester.jar、commons-fileupload.jar。

上述所有文件均可從Struts Web站點下載獲得,這些文件包含Struts framework以及相應的Apache Commons包,這些包是處理諸如國際化、集合操作、實用工具、驗證、日志記錄、digester以及文件上傳操作等特性所必需的。上述均為Struts支持的組件。本文不會用到上述所有功能,但是Struts依賴于其中的許多功能,例如,在解析Struts配置文件時就會大量用到digester功能。當需要使用日志記錄、文件上傳等服務時,這些功能就會派上用處。

因此,要將下列文件添加到WEB-INF文件夾中:struts-config.xml、struts-bean.tld、struts-html.tld、struts-logic.tld、struts-nested.tld、struts-template.tld、struts-tiles.tld。

其中struts-config.xml文件最為重要,該文件是Struts框架的主要配置文件,包含有所有的動作映射、數據源、插件等的定義。參見清單1中的例子。

清單1:Struts配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> <struts-config>  <!-- Data Sources --> <data-sources> </data-sources>  <!-- Form Beans -->  <form-beans>  </form-beans>  <!-- Global Exceptions -->  <global-exceptions>  </global-exceptions>  <!-- Global Forwards -->  <global-forwards>  </global-forwards>  <!-- Action Mappings -->  <action-mappings>  </action-mappings> </struts-config> 

作為Struts標簽庫定義文件,TLD文件可在JSP內部使用,執行各種有用操作,比如HTML呈現、邏輯處理或Tiles支持功能。這些文件可以從Struts 1.1發行版中獲得。

接下來,需要對Web部署描述符(web.xml)進行修改,指定Struts配置servlet的位置和相應參數。應將清單2中的代碼片斷添加到web.xml文件。

清單2:在web.xml中啟用Struts支持
<servlet>   <servlet-name>action</servlet-name>   <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>   <init-param>    <param-name>config</param-name>    <param-value>/WEB-INF/struts-config.xml</param-value>   </init-param>   <init-param>    <param-name>debug</param-name>    <param-value>2</param-value>   </init-param>   <init-param>    <param-name>detail</param-name>    <param-value>2</param-value>   </init-param>   <init-param>    <param-name>validate</param-name>    <param-value>true</param-value>   </init-param>   <load-on-startup>1</load-on-startup>  </servlet>  <servlet-mapping>   <servlet-name>action</servlet-name>   <url-pattern>*.do</url-pattern>  </servlet-mapping> 

清單2中的標簽定義Action Servlet的位置。Action Servlet是Struts主控制器,負責處理動作的生命周期,并將其映射到forward對象,此類對象由動作返回,并擁有兩個字段:名稱及路徑(通常為JSP文件的URL)。在這里指定struts-config.xml文件的位置,以及用于調試和驗證的參數。該servlet在啟動時加載,其加載順序為1,即第一個加載的servlet。如果在調用URL中檢測到*.do,就調用該servlet。

現在,我們必須將現有的servlet類轉換為動作類,并在struts-config.xml中為其定義適當映射。為了簡化這一過程,我們為所有動作提供一個抽象超類,請參見清單3。

清單3:抽象動作類
package actions; import javax.servlet.http.HttpServletRequest; 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; // This abstract class overrides Struts action class execute method and provides abstract  // performAction method to be overwritten by sub-classes.This helps us isolate some // common error processing into one place, rather than having it several places in the  // sub-classes. public abstract class AbstractAction extends Action {   public ActionForward execute(    ActionMapping mapping,    ActionForm form,    HttpServletRequest request,    HttpServletResponse response)    throws Exception {    // Define action errors and forward    ActionErrors errors = new ActionErrors();    ActionForward forward = new ActionForward();        try {              forward = performAction(mapping, form, request, response);    } catch (Exception e) {     // Report the error using the appropriate name and ID.     errors.add("name", new ActionError("id"));    }    // If a message is required, save the specified key(s)    // into the request for use by the tag. 

在該類中,我們實現了Struts 1.1框架對動作默認調用的execute方法。它在其performAction()方法中處理邏輯,并根據是否有異常拋出來轉到成功或失敗的處理程序。相應地,必須在Struts配置文件(struts-config.xml)中定義每一動作的成功和失敗映射。

具體動作的創建非常容易。可使用Eclipse向導,創建動作類。確保將AbstractAction選擇為超類,并復選Inherited abstract methods框(請參見圖2)。

圖2

這將自動生成帶有performAction()方法的CreateCustomerAction類。復制CreateCustomerServlet doGet()方法的內容,按清單4所示進行修改,并將其粘貼到performAction()。

清單4:新版本performAction方法的內容
   if (!errors.isEmpty()) {     saveErrors(request, errors);     // Forward control to the appropriate 'failure' URI      forward = mapping.findForward("failure");    } else {     // Forward control to the appropriate 'success' URI      if (forward == null) {      forward = mapping.findForward("success");     }    }    // Finish with    return (forward);   }      /**    * Perform appropriate actions as defined by the business logic    *     * @param mapping    * @param form    * @param request    * @param response    * @return    * @throws Exception    */   public abstract ActionForward performAction (     ActionMapping mapping,     ActionForm form,     HttpServletRequest request,     HttpServletResponse response)     throws Exception; } // create customer - get parameters first   String first_name = request.getParameter("first_name");   String last_name = request.getParameter("last_name");   String address = request.getParameter("address");   int cust_id = Math.abs((int)System.currentTimeMillis());      // create new customer object   Customer c = new Customer();   c.setId(cust_id);   c.setFirstName(first_name);   c.setLastName(last_name);   c.setAddress(address);      // construct and execute database command   DatabaseCommand command = new CreateCustomer(c);   int rows = (Integer)CommandExecutor.getInstance().executeDatabaseCommand(command);      return mapping.findForward("customer_created"); 
顯而易見,非Struts代碼和Struts代碼的惟一區別在于沒有使用下述代碼:
RequestDispatcher rd =  getServletContext(). getRequestDispatcher("/customer_created.jsp"); rd.forward(request, response);

而使用了下述更為簡單的代碼:
return mapping.findForward("customer_created");
我們不必再在我們的代碼中對JSP名稱進行硬編碼。相反,我們使用customer_created引用,該引用將在struts配置文件中被解析。在<action-mappings>標簽中,添加清單5中的片斷。

清單5:Create Customer Action的動作映射
 <action path="/CreateCustomer" type="actions.CreateCustomerAction">    <forward name="customer_created" path="/customer_created.jsp">    </forward>    <forward name="failure" path="/failure.jsp">    </forward>  </action>

在這個例子中,/CreateCustomer將作為調用該動作的URI。定義兩個forward():指向customer_created.jsp的customer_created,和指向顯示錯誤的failure.jsp的failure。有必要建立應用程序公共錯誤頁面,我們現在就來創建,請參見清單6。

清單6:Failure JSP文件
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"pageEncoding="ISO-8859-1"%> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Failure has occurred</title> </head> <body> <B>Errors occurred</B> <html:errors/> </body> </html> 

在本文件中,我們使用Struts HTML標簽庫顯示捕獲的錯誤。

同樣,我們將其他servlet轉化為Struts動作。別忘了更改index.html和其他文件中的URL,并在動作調用中添加".do"后綴。要在Tomcat服務器中部署新應用程序,使之與舊應用程序并存,需要將引用DTBest改為DBTestStruts。同時,將web.xml中的顯示名由DBTest改為DBTestStruts。

從DBTest應用程序源代碼和Web部署描述符中去掉舊的servlet定義,只留下動作和動作servlet定義。如欲刪除servlet包,只需在servlet包上右擊,選擇Delete,當出現確認提示時,選擇Yes。

要將新的應用程序部署到Tomcat,打開其控制臺http://localhost:8080/manager/html ,部署新的WAR文件。確保DBTest.xml已復制到DBTestStruts.xml,所有DBTest引用已改為DBTestStruts。

但是,另外一個問題是,原始解決方案的SQL直接在命令類中進行了硬編碼。在文章的下一節,將通過流行的Hibernate框架解決這個問題。該框架支持Java和關系數據庫之間的持久性。

添加Hibernate支持

Hibernate框架在下列領域提供幫助:
  1. 對象關系映射。它允許將Java對象和各個類之間的關系無縫映射到數據庫表及其關系。這是通過使用XML配置文件完成的,從而節約了開發人員花在編寫定制SQL查詢和從JDBC結果集構造對象上的大量時間。
  2. 連接管理。重用現有的數據庫連接是最重要的效率調優機制之一。
  3. 事務管理。在需要時啟動、提交和回滾事務的能力。如果使用JDBC驅動程序和支持XA的數據源,則還支持XA事務和兩階段提交。

如欲在本應用程序中使用Hibernate,請下載Hibernate框架的最新版本(當前最新版本為3.0)。解壓后將hibernate3.jar文件放到應用程序的WEB-INFlib目錄下。根據J2EE標準,這將自動將JAR文件添加到應用程序的編譯時和運行時類路徑。必須將dom4j.jar添加到WEB-INFlib目錄下,dom4j.jar文件也可以從Hibernate網站下載獲得。這是獲得Hibernate配置文件所需的XML解析器支持所必需的。

現在我們必須在應用程序層配置Hibernate。在Eclipse的Java Source文件夾中,創建hibernate.cfg.xml配置文件。這樣,部署好應用程序之后,該文件將自動進入WEB-INFclasses下的應用程序類路徑。

清單7:Hibernate配置文件
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.datasource">java:comp/env/jdbc/TestDB</property> <property name="show_sql">true</property> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <!-- Mapping files --> <mapping resource="hibernate.mapping.xml"/> </session-factory> </hibernate-configuration> 

清單7中顯示的文件包含對下列各項的引用:
  1. JDBC數據源引用。注意,必須使用完全合法的引用-java:comp/env/jdbc/TestDB。
  2. 在System.out信息傳入的Java系統控制臺中顯示生成的SQL的請求。
  3. 生成的SQL對話。我們使用MySQL數據庫。
  4. 對包含有域類和數據庫表之間映射的文件的引用。

hibernate.mapping.xml文件包含了應用程序使用的域對象和相應關系表的實體之間的映射信息。該文件應該與hibernate.cfg.xml文件一起,共同放在Java Source下的同一目錄。

清單8:Hibernate映射文件
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping>  <class name="domain.Customer" table="CUSTOMER" lazy="false">  <id name="id" column="ID"/>  <property name="firstName" column="FIRST_NAME"/>  <property name="lastName" column="LAST_NAME"/>  <property name="address" column="ADDRESS"/>  </class>  <class name="domain.Order" table="ORDERS" lazy="false">  <id name="id" column="ID"/>  <property name="custId" column="CUST_ID"/>  <property name="datePlaced" column="DATE_PLACED"/>  <property name="orderAmount" column="AMOUNT"/>  </class>  </hibernate-mapping> 

清單8中的文件包含了兩個類標簽,在標簽中,域類Customer和Order分別被映射到相應數據庫表中,每個實例變量被映射到數據庫中的一個列。lazy是一個值得一提的屬性。我們已經明確將其設定為false,這樣做的原因是,當lazy設定為默認的ture時,只有在訪問特定方法時,才會從數據庫讀取數據。比如,在進行SQL查詢時,只有在調用getFirstName()而不是預讀全部客戶數據時,才會從數據庫讀取數據。當讀取大量數據,或希望推遲進行開銷昂貴的數據庫操作時,這樣做或許會帶來好處。在我們的例子中,我們只讀取少量客戶信息,不會在以后進行其他操作或數據庫訪問。例如,如果在數據庫會話關閉后,我們仍然試圖調用lazy方法,Hibernate就會拋出異常。

配置完Hibernate之后,我們必須對CommandExecutor類稍作修改以使用該框架,并刪除硬編碼的SQL代碼。該類被作為單元素,用于存儲數據源并獲得數據庫連接。首先,我們添加實例變量以存儲Hibernate會話工廠。Hibernate會話工廠類似于數據源,不同的是,我們從Hibernate會話工廠獲得的不是數據庫連接,而是Hibernate數據庫會話。實例變量看上去類似于:
private SessionFactory  sessionFactory = null;

接下來,為實例變量創建一個訪問方法,請參見清單9。這樣做可以保存封裝的對象狀態,同時允許使用延遲初始化技術(只在需要時訪問數據)。

清單9:新的會話工廠訪問方法
// get hibernate session factorypublic SessionFactory getSessionFactory() {  if (sessionFactory == null)   {   sessionFactory = new Configuration().configure().buildSessionFactory();  }  return sessionFactory; } 

在該方法中,我們首次初始化一個會話工廠。Hibernate的Configuration對象被用于從類路徑讀取配置文件,從而初始化框架。

在開始使用Hibernate進行數據庫操作之前,我們已經使用了executeDatabaseCommand()方法:使用需要executeDatabaseOperation()方法的DatabaseCommand接口。由于現在希望使用Hibernate,我們將分別介紹使用DatabaseCommand接口和CommandExecutor單元素對象的其他方法。這種方法通過Hibernate框架執行所有的數據庫操作(請參見清單10)。

清單10:執行Hibernate命令
// execute a particular hibernate commandpublic Object executeHibernateCommand(DatabaseCommand c) throws Exception {  Session session = null;  try {   session = getSessionFactory().openSession();   Object o = c.executeHibernateOperation(session);   return o;  } catch (SQLException e) {   throw e;  } finally {   if (session != null) {    session.flush();    session.close();   }  } } 

看上去,它和executeDatabaseCommand()方法非常相似,不同之處在于我們使用的是Hibernate會話對象,而不是普通的JDBC連接。接下來,將下述存根方法添加到DatabaseCommand接口:

public Object executeHibernateOperation(Session session) throws SQLException;

在向接口添加新方法后,Eclipse工作臺中所有實現該接口的類均被標上紅色標記,因為這些實現該接口的類還必須實現它所需的所有方法。我們有四種實現數據庫命令接口的類。

public class CreateCustomer implements DatabaseCommand

public class CreateOrder implements DatabaseCommand

public class ListCustomers implements DatabaseCommand

public class ListCustomerOrders implements DatabaseCommand

因此,我們必須向這四種類中分別添加executeHibernateOperation()實現方法。首先我們來看看CreateCustomer類。該類的executeDatabaseOperation()方法如清單11所示。

清單11:CreateCustomer類的executeDatabaseOperation()方法
public Object executeDatabaseOperation(Connection conn) throws SQLException {   PreparedStatement sta = conn.prepareStatement   ("INSERT INTO CUSTOMER (ID, FIRST_NAME, LAST_NAME, ADDRESS) VALUES (?, ?, ?, ?)");   sta.setInt(1, cust.getId());   sta.setString(2, cust.getFirstName());   sta.setString(3, cust.getLastName());   sta.setString(4, cust.getAddress());   int rows_updated = sta.executeUpdate();   sta.close();   return new Integer(rows_updated);  } 

該方法比較冗長,并且要求編碼的開發人員對JDBC有所了解:比如,如何創建和執行預處理語句。此外,如果想要將數據庫從MySQL轉變為其他類型,開發人員需要重新編寫SQL,因為不同數據庫的SQL也許各不相同。使用Hibernate,只需要更改hibernate.cfg.xml配置文件中的SQL對話。清單12列出了我們的相應executeHibernateOperation()方法。

清單12:CreateCustomer類的executeHibernateOperation()方法
/** *  Execute Hibernate operation */ public Object executeHibernateOperation(Session session) throws SQLException {  session.save(cust);  session.flush();  return 1; } 

在清單12中,我們告訴會話對象將類保存到數據庫中。不需要SQL,不需要JDBC知識,不必對列和數據庫表名稱進行硬編碼。如果必須更改表或列名稱,我們也不必重新編寫應用程序中的多行代碼。Hibernate知道如何保存對象,不管該對象是否已經存在于數據庫中。執行INSERT或UPDATE操作,Hibernate可以以樂觀方式進行檢查(即嘗試執行UPDATE,如果失敗,則執行INSERT),或以悲觀方式進行檢查(即執行SELECT,檢查是否存在該行,如果存在,即執行UPDATE,否則執行INSERT)。命令執行后,將刷新會話并確保所有數據庫命令都已經被及時執行,框架緩沖區中沒有任何剩余。對CreateOrder類執行類似的操作。

首先,我們在JDBC版本中處理的兩個操作,CreateCustomer和CreateOrder,是數據庫插入操作。然而,想要讓應用程序工作,還必須處理數據庫查詢。我們使用ListCustomers和ListCustomerOrders命令來實現這一功能。我們先來看看如何獲取用戶列表。

清單13:ListCustomers類的executeDatabaseOperation()方法
public Object executeDatabaseOperation(Connection conn) throws SQLException {  // List customers in the database      ArrayList list = new ArrayList();   Statement sta = conn.createStatement();   ResultSet rs = sta.executeQuery("SELECT ID, FIRST_NAME, LAST_NAME, ADDRESS FROM CUSTOMER");   while(rs.next()) {    Customer cust = new Customer();    cust.setId(rs.getInt(1));    cust.setFirstName(rs.getString(2));    cust.setLastName(rs.getString(3));    cust.setAddress(rs.getString(4));    list.add(cust);   }   rs.close();   sta.close();   return list; } 

清單13包含了大量JDBC調用。首先,創建SQL語句,并使用硬編碼的列和表名執行該語句,接下來,檢查數據庫查詢的ResultSet,根據從數據庫表讀取的行數據,構建一個用戶域對象,同時記住列順序或名稱。這些操作都易于出錯,同時一旦需要更改數據庫表,這些操作都難以維持。而Hibernate正好可以解決這些問題。Hibernate引入了名為Hibernate Query Language (Hibernet查詢語言,HQL)的全新語言,在這種語言中,用戶不用查詢數據庫表,只需要查詢對象。清單14列出了我們使用的executeHibernateOperation()方法。

清單14:ListCustomers類的executeHibernateOperation()方法
/** *  Execute Hibernate select operation  */ public Object executeHibernateOperation(Session session) throws SQLException {  Query q = session.createQuery("from customer in class domain.Customer");  Iterator iter = q.iterate();  ArrayList list = new ArrayList();  while(iter.hasNext()) {   Customer cust = (Customer)iter.next();   list.add(cust);  }  return list; } 

同樣,這種方法看起來也非常類似于前面的方法。然而,也存在一些顯著的區別。這里我們用到了眾多Hibernate對象。第一個對象就是Query類。通過該類,用戶可以使用HQL(通過createQuery方法)或普通SQL(通過createSQLQuery方法)創建并執行數據庫查詢。我們來看看這里用到的HQL:

from customer in class domain.Customer

基本上我們選擇了domain.Customer類中的customer變量所識別的所有客戶。獲取查詢迭代器就可以讓我們將customers放置到任何集合。在此處,就是ArrayList<Customer>。

可以為ListCustomerOrders類編寫一個非常相似的方法,但是HQL要復雜一些,如清單15所示。

清單15:ListCustomerOrders類的executeHibernateOperation()方法
/** *  Execute Hibernate query operation  */ public Object executeHibernateOperation(Session session) throws SQLException {  Query q = session.createQuery("from order in class domain.Order where order.custId = '" + this.cust_id + "'");  Iterator iter = q.iterate();  ArrayList list = new ArrayList();  while(iter.hasNext()) {   Order order = (Order)iter.next();   list.add(order);  }  return list; } 

在本例中,我們在查詢中使用where子句。注意,在where子句中,我們可以使用Order類的實例變量(custId)進行查詢。其語法與Java的句點表達法相似。

最后,我們必須更新Struts動作類,以調用executeHibernateOperation()方法,而不是executeDatabaseOperationMethod()方法。使用Eclipse編輯器可以輕松完成這一任務。

將Eclipse項目導出到WAR

使用WTP工具,我們可以輕松地將項目導出到WAR文件,并部署在Tomcat中。選擇DBTestStruts Web項目,再從File菜單中選擇Export。出現提示信息后,選擇WAR文件,指定文件名,WAR文件就可以部署到Tomcat了。

結束語

本文旨在探討如何在使用Eclipse和WTP工具開發的簡單Web應用程序中集成Struts和Hibernate支持。這些框架有助于改進應用程序的可維護性、代碼的可重用性以及代碼的可讀性。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗