ZK/操作指南/整合其他框架
1. 將 Spring 框架 jar 和其所有依賴項新增到伺服器的類路徑中。 (有關詳細資訊,請參閱 http://www.springframework.org 上的文件)。
2. 將以下內容新增到您的 web.xml 中
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
3. 將以下 applicationContext.xml 檔案放入您的 WEB-INF 目錄中
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="myBusinessServiceSpringBean" class="test.BusinessServiceImpl"/> </beans>
4. test.BusinessServiceImpl 和 test.BusinessService 的示例
package test;
import java.util.*;
public class BusinessServiceImpl implements BusinessService
{
public List getElementsList()
{
// some logic here
return Arrays.asList(new String[] {"abc", "def"});
}
}
package test;
public interface BusinessService
{
java.util.List getElementsList();
}
5. 編譯並部署。
6. 最後,一個呼叫 Spring 管理的 BusinessService 方法的 ZUL 檔案示例
<?xml version="1.0" encoding="windows-1251"?>
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<window>
<grid>
<rows>
<row forEach="${myBusinessServiceSpringBean.elementsList}">
<label value="${each}"/>
</row>
</rows>
</grid>
</window>
其中 forEach 迴圈遍歷由對 Spring bean 方法的呼叫返回的集合,而 ${each} 屬性會導致對集合中每個物件呼叫 toString。 您也可以使用 zscript 直接操作您的 Spring bean。 此程式碼與上述程式碼執行相同的操作
<?xml version="1.0" encoding="windows-1251"?>
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<zscript>
List someData = myBusinessServiceSpringBean.getElementsList();
</zscript
<window>
<grid>
<rows>
<row forEach="${someData}">
<label value="${each}"/>
</row>
</rows>
</grid>
</window>
如果您選擇使用 MVC 模式(請參閱此維基中的 Composer 和 GenericAutowireComposer 示例),那麼您可以讓 Spring 建立您的模型和控制器 bean。 考慮以下 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="myModel" scope="session" class="com.acme.MyModelImpl" />
<bean id="myController" scope="prototype" class="com.acme.MyControllerImpl">
<constructor-arg ref="myModel" />
</bean>
</beans>
在這裡,我們告訴 Spring 為給定的使用者 HttpSession 建立一個 com.acme.MyModelImpl 物件,但為每個應用程式查詢建立新的 com.acme.MyControllerImpl bean。 每個“myController” bean 都是使用一個建構函式建立的,該建構函式傳遞了範圍為 HttpSession 的“myModel” bean。 我們需要在 web.xml 中啟用 RequestContextListener 才能使會話範圍生效
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener>
現在在任何給定頁面內,我們都可以讓 ZK 要求 Spring 為一個給定的視窗物件使用一個新的 myController bean 作為控制器
<?xml version="1.0" encoding="UTF-8"?>
<zk xmlns="http://www.zkoss.org/2005/zul">
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<window id="mainWindow" apply="${myController}">
<textbox forward="onChange=mainWindow.onUserEntersDiscountCouponNumber"/>
</window>
上面的程式碼使用了 DelegatingVariableResolver。 它透過 Spring 解析“apply”屬性中的 myController 引用(請參閱 MVC 和“apply”屬性文件)。 Spring 按需建立與會話範圍的 myModel bean 連線的新物件。“forward”屬性將委託文字框 onChange 事件到 mainWindow 的自定義“onUserEntersDiscountCouponNumber” 事件處理程式。 Window 元件類沒有這樣的事件處理程式,但是“apply”屬性中指定的物件可以向它應用到的視窗物件新增一個事件處理程式。 最簡單的實現方法是讓 com.acme.MyControllerImpl 控制器類子類化 GenericAutowireComposer 並提供一個事件處理程式“public void onUserEntersDiscountCouponNumber(Event event) throws Exception”。 然後,超類中的邏輯將自動向 mainWindow 物件新增一個事件處理程式,該處理程式呼叫您提供的事件處理程式。 請參閱此維基中有關 MVC 的示例,以瞭解詳細資訊以及在 http://www.zkoss.org 上釋出的 MVC 小話題
注意 比以下方法更輕量級的方法是開啟 ZKFilter 並讓它對您操作的 URL 進行後處理。 這隻有在您的 JSP 輸出純 XHTML 時才有效(使用 jTidy -asxml 將 html 轉換為 xhtml,然後將 CDATA 部分放在頁面中的任何指令碼原始碼周圍)。
注意 使用 ZK3.0 時,請考慮使用 ZK 自定義標籤庫,該標籤庫允許您使用標籤庫將 ZK 元件寫入頁面。
ZK 提供了非常豐富的功能集。 您可以使用它構建整個單頁 Ajax 驅動的富 Web 應用程式,而無需使用任何 Web 程式設計。 然而,許多公司在現有的 J2EE Web 技術(如 Struts、Tiles 和 JSP)方面投入了大量資金。 這些公司可能希望透過在那些從 XUL 元件中受益的頁面上的中心磁貼中開始使用 ZK XUL 來利用其現有投資。 本節概述了一種實現此目的的方法。 您需要熟悉 Spring 中對 Struts 的支援才能遵循此示例。
由於此操作指南涵蓋了 ZK、Struts 和 Spring,因此您需要在 web.xml 中指定它們(請注意,zkFilter 沒有載入,因為 Tiles 會導致 zkLoader 渲染純 zul)
<!-- SECTION FOR SPRING --> <listener> <display-name>Spring Context Loader</display-name> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- SECTION FOR ZK --> <listener> <description>Used to cleanup when a session isdestroyed</description> <display-name>ZK Session Cleaner</display-name> <listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class> </listener> <servlet> <description>ZK loader for evaluating ZK pages</description> <servlet-name>zkLoader</servlet-name> <servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class> <!-- Specifies URI of the update engine(DHtmlUpdateServlet). --> <init-param> <param-name>update-uri</param-name> <param-value>/zkau</param-value> </init-param> <load-on-startup>1</load-on-startup><!-- MUST --> </servlet> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>*.zul</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>*.zhtml</url-pattern> </servlet-mapping> <servlet> <description>The asynchronous update engine for ZK</description> <servlet-name>auEngine</servlet-name> <servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>auEngine</servlet-name> <url-pattern>/zkau/*</url-pattern> </servlet-mapping> <!-- SECTION FOR STRUTS --> <servlet> <servlet-name>The Struts Engine</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts/struts-config.xml</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>
建立您的 struts 配置檔案 struts-config.xml
<?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> <form-beans> <!-- This Form Bean will hold the HTTP POST data sent from our /search.jsp page --> <form-bean name="SearchStuff" type="com.yourdomain.web.StuffSearchForm"></form-bean> </form-beans> <action-mappings> <!-- This Action Mapping delegates to Spring to find a Bean called SearchStuff --> <action path="/SearchStuff" name="SearchStuff" type="org.springframework.web.struts.DelegatingActionProxy" validate="false" scope="request"> <forward name="success" path="/search-results-tile.jsp"/> <forward name="errors" path="/search-errors-tile.jsp"/> </action> </action-mappings> <controller processorClass="org.apache.struts.action.RequestProcessor"></controller> <!-- Message Resources --> <message-resources parameter="ApplicationResources" /> <!-- Spring support for Struts --> <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="contextConfigLocation" value="/WEB-INF/spring/applicationContext.xml" /> </plug-in> </struts-config>
然後在您的 Spring 配置檔案 applicationContext.xml 中
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- Struts will delegate onto this custom struts Action Class --> <bean name="/SearchStuff" class="com.yourdomain.web.StuffSearchAction"> <property name="searchDelegate"> <ref bean="searchDelegate" /> </property> </bean> <!-- The Action Class needs a Business Delegate --> <bean id="searchDelegate" class="com.yourdomain.business.SearchDelegateImpl"> <constructor-arg><ref bean="pojoSessionFacade"/></constructor-arg> </bean> <!-- The Business Delegate needs a Pojo Facade that implements a package of use cases --> <bean id="pojoSessionFacade" class="com.yourdomain.business.PojoSessionFacadeImpl"> <constructor-arg>...</constructor-arg> <constructor-arg>...</constructor-arg> </bean> <!-- The pojo sesion facade should be wrapped in a spring transaction interceptor. Working open source code examples are at this link http://www.manning.com/crichardson/. The code is Apache licienced and will compile, build and unit test a working pojo Session Facade using either JDO, Hibernate or iBATIS sql maps. See his applicationContext.xml for details. --> ... </beans>
請注意,在 Struts 配置中,我們有兩個操作對映,分別對應“成功”或“錯誤”。 成功對映到 search-results-tile.jsp,其中包含
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:insert page="site-layout.jsp" flush="true"> <tiles:put name="body-content" value="/search-results.zul" /> </tiles:insert>
這基本上表示此頁面看起來像 site-layout,但在頁面中間有 search-results.zul。 這是因為在 site-layout.jsp 中存在
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <html> <head>...</head> <body> <div class="main"> <div class="banner"> <!-- lots of html --> .... </div> <div class="left-menu"> <!-- The site menu could be a JSP using JSTL and custom tags --> <tiles:insert attribute="site-menu" /> </div> <div class="main-panel"> <!-- The main panel could be either a .jsp or a .zul--> <tiles:insert attribute="body-content" /> </div> ... </div> </body> </html>
有了這些資訊,我們看到在特定的 search-results-tile.jsp 中,我們宣告它基於 site-layout,但在 body-content 位置使用 search-results.zul。
接下來,我們需要確保 Struts Action 透過 HTTP Session 將結果傳遞到 search-results.zul。 為此,Struts Action 可以執行以下操作
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
StuffSearchForm stuffSearchForm = (StuffSearchForm)form;
ActionErrors errors = stuffSearchForm.validate(mapping, request);
if(!errors.isEmpty()){
saveErrors(request, errors);
return mapping.findForward("errors");
}
// Business logic. The protected field this.searchDelegate is an interface type. The concrete
// instance was configured by Spring and injected when this object was constructed.
List searchResultsList = this.searchDelegate.searchStuff(stuffSearchForm.getStuffSearchCriteria());
// Put the list where ZUL can get at it in the web session
request.getSession().setAttribute("searchResults", searchResultsList);
return mapping.findForward("success");
}
請注意,this.searchDelegate 將由 Spring 設定,如 applicationContext.xml 中定義的那樣
因此,我們現在知道我們的 Struts 類將搜尋結果列表放入 HTTP Session 中,我們的 search-results.zul 可以從中找到它
<window>
<zscript><![CDATA[
/**
* Here we pick up the search results that were set by our Struts Action class.
*/
List iterateOverMe = sessionScope.get("searchResults");
]]></zscript>
<tabbox>
<tabs>
<!-- Iterate over our List once to draw the tabpanel tabs. -->
<tab forEach="${iterateOverMe}" label="${each.searchResultLabel}"/>
</tabs>
<tabpanels>
<!-- Iterate over our Collection again to draw the tabpanel panels. -->
<tabpanel forEach="${iterateOverMe}">
<!--
Here you put whatever ZUL that you need to render your search
result items using ${each}. See other examples in this wiki.
-->
...
</tabpanel>
</tabpanels>
</tabbox>
</window>
但是,您應該意識到,讓 Struts 為我們執行搜尋會使我們的伺服器進行比必要更多的繁重的頁面重建工作。 更加以 AJAX 為中心的做法是編寫一個單一的 ZUL 頁面,首先向使用者呈現一個搜尋輸入表單。 然後,在 zscript 事件處理程式中執行搜尋並使用搜索結果更新 ZK 桌面。 只有搜尋結果的 html 才會在 AJAX 響應中傳送回瀏覽器。 實現此目的的一種模式是將最初位於 Struts Action 子類中的程式碼移動到 org.zkoss.zul.Window 的子類中,然後在 zscript 事件處理程式中呈現搜尋結果
<?page id="main-page"?>
<!-- NOTE THAT WE SPECIFY OUR OWN CUSTOM BUSINESS DELEGATE SUBCLASS OF WINDOW HERE! -->
<window use="com.yourdomain.zul.SearchWindow" id="SearchFacadeDelegate">
<zscript>
void doSearch(){
businessFacadeDelegate = Path.getComponent('/SearchFacadeDelegate');
businessFacadeDelegate.setSearchNameCriteria(name_criteria.value);
searchResultsPlaceHolder.src = "render-search-results.zul";
// The file render-search-results.zul should have the delegate run
// the actual search and render the results
}
</zscript>
<label value="Search By Name:"/><textbox name="name_criteria"/>
<button label="Search">
<attribute name="onClick">
doSearch();
</attribute>
</button>
<include id="searchResultsPlaceHolder" src=""/>
</window>
注意 - 使用 Tiles 時,ZUL 頁面可能不會注意到它被包含,並且它可能會將 <html> 和 <head> 標籤寫入頁面。 為了避免這種情況,而不是讓磁貼包含原始 zul 頁面,請包含一個 jsp 頁面,該頁面僅 JSP 包含 zul 頁面,這將表現良好。
有關介紹,請參閱此 小話題。
沒有 Spring 的 ZK Hibernate 支援:Hibernate 會話處理。 初步!
ZK 團隊從 2.1.1 版開始提供了一組類來支援沒有 Spring 的 Hibernate。 Henri Chen 在他的 Hibernate + ZK 文章中解釋了詳細資訊。
有關介紹,請參閱此 小話題
Hari Gangadharan 還寫了另一個小話題 - 讓 Acegi 與 ZK 一起工作
作者:Marcos de Sousa (馬普托 - 莫三比克)
01/06/2007
此條目背後的動機是為整合 ZK 框架 2.x.x + Spring 框架 2.x.x + Hibernate 3.x 提供一個簡單的分步指南。 我將向您展示如何在真實的 J2EE 世界應用程式中完成操作。
A. 初始設定
1. 下載 ZK 框架 JAR 並新增到您的類路徑中:zk.jar, zul.jar, zhtml.jar, zkplus.jar, zweb.jar, zcommon.jar, bsh.jar, jcommon.jar, commons-el.jar, commons-fileupload.jar, commons-io.jar, Filters.jar
2. 下載 Spring 框架並新增到您的類路徑中:spring.jar, spring-mock.jar
3. 下載 Hibernate 3.x.x 和 Hibernate Annotation 3.x.x,然後將其新增到您的類路徑中:antlr-2.x.x.jar, asm.jar, asm-attrs.jar, cglib-2.x.x.jar, commons-collections 2.x.x.jar, commons-logging-1.x.x.jar, dom4j-1.x.x.jar, ejb3-persistence.jar, hibernate3.jar, hibernate-annotations.jar, jdbc2_0-stdext.jar, jta.jar, xerces-2.x.x, xml-apis.jar
4. 新增包含您的資料庫驅動程式的 JAR(我在示例中使用 SQL Server sqljdbc.jar,但只需要進行少量更改即可適應其他資料庫)。
5. 新增 JAR junit-3.x.x.jar 用於單元測試。
B. 程式碼 - 域模型
本示例將基於一個簡單的域模型,僅包含兩個類。請注意註解的使用。你可以選擇使用註解或 XML 檔案來指定物件關係對映元資料,甚至可以組合使用兩種方法。這裡我選擇僅使用註解,並在域模型程式碼列表之後提供簡要說明。
首先是 **Employee** 類
package com.wikibook.persistence;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
@SuppressWarnings("serial")
@Entity
public class Employee implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String fullname;
private String email;
private String username;
private String password;
@ManyToOne
@JoinColumn(name = "role")
private Role role;
public Employee() {
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
其次是 **Role** 類
package com.wikibook.persistence;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@SuppressWarnings("serial")
@Entity
public class Role implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<Employee> employees = new HashSet<Employee>();
public Role() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Employee> getEmployees() {
return employees;
}
public void setEmployees(Set<Employee> employees) {
this.employees = employees;
}
public void addEmployee(Employee employee) {
this.employees.add(employee);
}
}
在 *Employee* 類中,有一個 *@ManyToOne* 註解來描述與 *Role* 的關係。請檢視 *cascade* 規則。例如,當一個 *Role* 被刪除時,與其關聯的 *Employees* 也會被刪除。
C. 程式碼 - 資料訪問物件層
為了訪問域模型的例項,最好建立一個通用的介面,隱藏底層持久化機制的所有細節。這樣,如果以後切換到除 Hibernate Annotation 之外的其他東西,就不會對架構造成影響,只需進行少量更改即可適應遷移到 JPA。這也有助於更輕鬆地測試服務層,因為它允許建立此資料訪問介面的存根實現,甚至動態模擬實現。
以下是該介面。請注意,這裡沒有任何對 Hibernate Annotation 或 Spring 類的依賴。實際上,這裡唯一不是核心 Java 類的依賴項是我的域模型的類。
package com.wikibook.persistence.dao;
import java.util.List;
import com.wikibook.persistence.Employee;
public interface EmployeeDao {
void save(Employee employee);
void update(Employee employee);
void delete(Employee employee);
Employee get(Long id);
List<Employee> getAll();
Employee getByUserName(String username);
List<Employee> getAllByRole(Integer role);
}
package com.wikibook.persistence.dao;
import java.util.List;
import com.wikibook.persistence.Role;
public interface RoleDao {
void save(Role role);
void update(Role role);
void delete(Role role);
Role get(Long id);
List<Role> getAll();
Role getByName(String name);
}
為了實現此介面,我將擴充套件 Spring 的 *HibernateDaoSupport* 類。這提供了一個獲取 *HibernateTemplate* 的便捷方法。如果你以前使用過帶有 JDBC 或其他 ORM 技術的 Spring,那麼你可能對這種方法相當熟悉。
需要注意的是,使用 HibernateDaoSupport 是可選的。
以下是實現
package com.wikibook.persistence.dao;
import java.util.List;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.wikibook.persistence.Employee;
public class EmployeeDaoImpl extends HibernateDaoSupport implements
EmployeeDao {
public void save(Employee employee) {
getHibernateTemplate().save(employee);
}
public void update(Employee employee) {
getHibernateTemplate().update(employee);
}
public void delete(Employee employee) {
getHibernateTemplate().delete(employee);
}
public Employee get(Long id) {
return (Employee)getHibernateTemplate().get(Employee.class, id);
}
@SuppressWarnings("unchecked")
public List<Employee> getAll() {
return getHibernateTemplate().loadAll(Employee.class);
}
@SuppressWarnings("unchecked")
public Employee getByUserName(String username) {
List<Employee> lista = getHibernateTemplate().find("FROM Employee WHERE username = ?", username);
if (!lista.isEmpty()) {
return lista.get(0);
}
return null;
}
@SuppressWarnings("unchecked")
public List<Employee> getAllByRole(Integer role) {
return getHibernateTemplate().find(
"FROM Employee WHERE role = ?", role);
}
}
package com.wikibook.persistence.dao;
import java.util.List;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.wikibook.persistence.Role;
public class RoleDaoImpl extends HibernateDaoSupport implements RoleDao {
public void save(Role role) {
getHibernateTemplate().save(role);
}
public void update(Role role) {
getHibernateTemplate().update(role);
}
public void delete(Role role) {
getHibernateTemplate().delete(role);
}
public Role get(Long id) {
return (Role)getHibernateTemplate().get(Role.class, id);
}
@SuppressWarnings("unchecked")
public List<Role> getAll() {
return getHibernateTemplate().loadAll(Role.class);
}
@SuppressWarnings("unchecked")
public Role getByName(String name) {
List<Role> lista = getHibernateTemplate().find("FROM Role WHERE name = ?", name);
if (!lista.isEmpty()) {
return lista.get(0);
}
return null;
}
}
D. 服務層
服務層在系統架構中起著至關重要的作用。它將是事務分界點所在,通常,事務將在 Spring 配置中宣告式地分界。在下一步中,當你檢視配置時,你會注意到我已經提供了一個 *transactionManager* bean。它將使用事務包裝服務層方法。關鍵點是資料訪問層中沒有與事務相關的程式碼。使用 Spring *HibernateTemplate* 可確保在所有 DAO 中共享相同的 *EntityManager*。因此,事務傳播會自動發生,就像在 Spring 框架內配置的其他持久化機制一樣。
以下是介面和實現
package com.wikibook.persistence.manager;
import java.util.List;
import com.wikibook.persistence.Employee;
public interface EmployeeManager {
void save(Employee employee);
void update(Employee employee);
void delete(Employee employee);
Employee get(Long id);
List<Employee> getAll();
Employee getByUserName(String username);
List<Employee> getAllByRole(Integer role);
}
package com.wikibook.persistence.manager;
import java.util.List;
import com.wikibook.persistence.Role;
public interface RoleManager {
void save(Role role);
void update(Role role);
void delete(Role role);
Role get(Long id);
List<Role> getAll();
Role getByName(String name);
}
package com.wikibook.persistence.manager;
import java.util.List;
import com.wikibook.persistence.Employee;
import com.wikibook.persistence.dao.EmployeeDao;
public class EmployeeManagerImpl implements EmployeeManager {
private EmployeeDao employeeDao;
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public void save(Employee employee) {
this.employeeDao.save(employee);
}
public void update(Employee employee) {
this.employeeDao.update(employee);
}
public void delete(Employee employee) {
this.employeeDao.delete(employee);
}
public Employee get(Long id) {
return this.employeeDao.get(id);
}
public List<Employee> getAll() {
return this.employeeDao.getAll();
}
public Employee getByUserName(String username) {
return this.employeeDao.getByUserName(username);
}
public List<Employee> getAllByRole(Integer role) {
return this.employeeDao.getAllByRole(role);
}
}
package com.wikibook.persistence.manager;
import java.util.List;
import com.wikibook.persistence.Role;
import com.wikibook.persistence.dao.RoleDao;
public class RoleManagerImpl implements RoleManager {
private RoleDao roleDao;
public void setRoleDao(RoleDao roleDao) {
this.roleDao = roleDao;
}
public void save(Role role) {
this.roleDao.save(role);
}
public void update(Role role) {
this.roleDao.update(role);
}
public void delete(Role role) {
this.roleDao.delete(role);
}
public Role get(Long id) {
return this.roleDao.get(id);
}
@SuppressWarnings("unchecked")
public List<Role> getAll() {
return this.roleDao.getAll();
}
public Role getByName(String name) {
return this.roleDao.getByName(name);
}
}
E. 配置
由於我選擇了基於註解的對映,因此你實際上已經看到了大部分 Hibernate Annotation 特定的配置,這些配置是在展示域類時提供的。如上所述,也可以透過 XML 配置這些對映。唯一其他必需的配置是在 *META-INF/context.xml*、Spring 應用程式上下文 *spring-sql.xml*(必須位於類路徑或包開始的位置)和 *web.xml* 中。
以下是 **context.xml**
<?xml version="1.0" encoding="UTF-8"?> <Context crossContext="true" reloadable="true"> <Resource name="DefaultDS" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory" driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:sqlserver://COMPUTERNAME:1433;DatabaseName=WIKIBOOK" username="USERNAME" password="PASSWORD" maxActive="10" maxIdle="10" maxWait="-1" /> </Context>
以下是 **spring-sql.xml**(必須位於類路徑根目錄)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd"> <!-- JNDI DataSource for J2EE environments --> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/DefaultDS" /> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="annotatedClasses"> <list> <value>com.wikibook.persistence.Role</value> <value>com.wikibook.persistence.Employee</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.dialect"> org.hibernate.dialect.SQLServerDialect </prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="daoTxTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager" /> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="roleDao" class="com.wikibook.persistence.dao.RoleDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="roleManager" parent="daoTxTemplate"> <property name="proxyInterfaces"> <list> <value> com.wikibook.persistence.manager.RoleManager </value> </list> </property> <property name="target"> <bean class="com.wikibook.persistence.manager.RoleManagerImpl"> <property name="roleDao" ref="roleDao" /> </bean> </property> </bean> <bean id="employeeDao" class="com.wikibook.persistence.dao.EmployeeDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="employeeManager" parent="daoTxTemplate"> <property name="proxyInterfaces"> <list> <value> com.wikibook.persistence.manager.EmployeeManager </value> </list> </property> <property name="target"> <bean class="com.wikibook.persistence.manager.EmployeeManagerImpl"> <property name="employeeDao" ref="employeeDao" /> </bean> </property> </bean> </beans>
以下是 **web.xml**
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <description> <![CDATA[WikiBook]]> </description> <display-name>WikiBook</display-name> <!-- Spring ApplicationContext --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/spring-*.xml</param-value> </context-param> <!-- INI ZK --> <!-- INI DSP --> <servlet> <description> <![CDATA[The servlet loads the DSP pages.]]> </description> <servlet-name>dspLoader</servlet-name> <servlet-class> org.zkoss.web.servlet.dsp.InterpreterServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>dspLoader</servlet-name> <url-pattern>*.dsp</url-pattern> </servlet-mapping> <!-- END DSP --> <servlet> <description>ZK loader for ZUML pages</description> <servlet-name>zkLoader</servlet-name> <servlet-class> org.zkoss.zk.ui.http.DHtmlLayoutServlet </servlet-class> <init-param> <param-name>update-uri</param-name> <param-value>/zkau</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>*.zul</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>*.zhtml</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>zkLoader</servlet-name> <url-pattern>/zk/*</url-pattern> </servlet-mapping> <servlet> <description>The asynchronous update engine for ZK</description> <servlet-name>auEngine</servlet-name> <servlet-class> org.zkoss.zk.au.http.DHtmlUpdateServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>auEngine</servlet-name> <url-pattern>/zkau/*</url-pattern> </servlet-mapping> <!-- END ZK --> <listener> <description> Used to cleanup when a session is destroyed </description> <display-name>ZK Session Cleaner</display-name> <listener-class> org.zkoss.zk.ui.http.HttpSessionListener </listener-class> </listener> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> <!-- INI filter --> <!-- Hibernate OpenSession Filter --> <filter> <filter-name>lazyLoadingFilter</filter-name> <filter-class> org.springframework.orm.hibernate3.support.OpenSessionInViewFilter </filter-class> </filter> <filter-mapping> <filter-name>lazyLoadingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- END filter --> <!-- Miscellaneous --> <session-config> <session-timeout>10</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.zul</welcome-file> </welcome-file-list> <!-- INI resource-ref --> <resource-ref> <description>DefaultDS</description> <res-ref-name>jdbc/DefaultDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> <!-- END resource-ref --> <!-- INI MIME mapping --> <!-- MIME mapping OMMITED--> <!-- END MIME mapping --> </web-app>
F. 整合測試
也許學習新 API 的最佳方法是編寫大量測試用例。*TestEmployeeManager* 類提供了一些基本測試。為了進一步瞭解 Hibernate Annotation,請修改程式碼和/或配置,並觀察對這些測試的影響。
以下是 **TestManager** 類
package com.wikibook.test;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.springframework.test.AbstractTransactionalSpringContextTests;
public class TestManager extends AbstractTransactionalSpringContextTests {
public TestManager() {
try {
if (SimpleNamingContextBuilder.getCurrentContextBuilder() == null) {
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
DataSource ds = new DriverManagerDataSource("com.microsoft.sqlserver.jdbc.SQLServerDriver",
"jdbc:sqlserver://COMPUTERNAME:1433;DatabaseName=WIKIBOOK", "USERNAME", "PASSWORD");
builder.bind("java:comp/env/DefaultDS", ds);
builder.activate();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected String[] getConfigLocations() {
return new String[] { "spring-sql.xml" };
}
}
以下是 **TestEmployeeManager** 類
package com.wikibook.test;
import com.wikibook.persistence.Employee;
import com.wikibook.persistence.manager.EmployeeManager;
public class TestEmployeeManager extends TestManager {
private EmployeeManager employeeManager;
public TestEmployeeManager() {
}
@Override
protected void onSetUpBeforeTransaction() throws Exception {
employeeManager = (EmployeeManager) applicationContext.getBean("employeeManager");
}
public void testInsert() {
Employee employee = new Employee();
employee.setFullname("Marcos de Sousa");
employee.setUsername("marcos.sousa");
employee.setPassword("password1");
employeeManager.save(employee);
employee = new Employee();
employee.setFullname("Tom Yeh");
employee.setUsername("tom.yeh1");
employee.setPassword("password2");
employeeManager.save(employee);
employee = new Employee();
employee.setFullname("Henri Chen");
employee.setUsername("henri.chen");
employee.setPassword("password3");
employeeManager.save(employee);
setComplete(); // Commit the transactions, and don't rollback
}
public void testUpdate() {
Employee employee = employeeManager.get(Long.valueOf(2));
assertEquals("Must be tom.yeh1", "tom.yeh1", employee.getUsername());
employee.setUsername("tom.yeh");
employeeManager.update(employee);
employee = employeeManager.get(Long.valueOf(2));
assertEquals("Must be tom.yeh", "tom.yeh", employee.getUsername());
setComplete();
}
public void testDelete() {
assertEquals("Before must be 3", 3, employeeManager.getAll().size());
Employee employee = employeeManager.get(Long.valueOf(3));
employeeManager.delete(employee);
assertEquals("After must be 2", 2, employeeManager.getAll().size());
setComplete();
}
}
以下是 **AllTests** 類
package com.wikibook.test;
import junit.framework.Test;
import junit.framework.TestSuite;
public class AllTests {
public static Test suite() {
TestSuite suite = new TestSuite("Test for com.wikibook.test");
//$JUnit-BEGIN$
suite.addTestSuite(TestEmployeeManager.class);
// TODO: ADD MORE TEST SUITE HERE. For Example TestRoleManager
//$JUnit-END$
return suite;
}
}
到這裡為止,如果你執行你的單元測試,一切都會正常工作。
// TODO: 明天我會繼續...
// TODO: 建立資料庫、表,展示與 ZK 框架的整合作為前端、結論和技巧,等等。
// TODO: 也許展示 Acegi Security?OSWORKFLOW?IBM AS400?Ant?Maven 2?SSL/HTTPS?CSS?Eclipse?在實際應用中。