<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    使用 JSF 構建數據庫驅動的應用程序

    開發基于 JSF 的 Web 應用程序,這些應用程序使用 Oracle TopLink 和 JSTL 的 SQL 標記更新和查詢關系數據庫

    本文相關下載:
    示例代碼
    Oracle TopLink 10g
    Oracle 數據庫 10g
    OC4J 10g
    Oracle ADF 組件
    JavaServer Faces

    JavaServer Faces (JSF) 是一個人們期待已久的標準 Java 技術,用于創建基于 Web 的用戶界面。本文將介紹如何構建 JSF 表單、如何使用 JSF 驗證表單數據、如何實現訪問數據庫的 JSF 操作以及如何使用 JSF 呈現 SQL 結果集。可以使用低級 API (JDBC)、對象關系 (O-R) 映射框架(如 Oracle TopLink)或 JSP 標記庫(例如,JSTL)更新和查詢關系數據庫。本文通篇介紹了這些選件以及主要的 JSF 特性。

     

    應用程序概述

    JSF 并非僅是另一個用于 Web 開發的標記庫。它是一個基于 MVC 的框架,用于控制 Web 表單和管理 JavaBean。JSF 提供了一組有限的標準 UI 組件,但您可以使用組件庫(如 Oracle 應用程序開發框架 (ADF) Faces),還可以構建您自己的定制組件。注意,由于所有這些 UI 組件均基于標準的 JSF API,因此它們可以在同一網頁中一起使用。

    JSF 頁面是常規的 JSP,它們使用標準 JSF 標記庫和/或其他基于 JSF API 的庫。執行 JSF 頁面時,JSF 框架嘗試獲取或恢復所謂的“組件樹”(或“視圖”)。它包含 Web 頁面組件的信息、必須執行的數據轉換和驗證、UI 狀態必須存儲到的 bean 屬性、注冊的事件監聽器等。JSF 創建組件樹(如果它不存在),然后將它保存到客戶端(使用隱藏的表單域)或服務器(作為會話屬性)以備后續請求使用。

    本文提供了一個供用戶訂閱幾個時事通訊的示例 Web 應用程序。訂戶通過提供他們的電子郵件地址、姓名和首選項進行注冊。他們還必須選擇一個口令,以便以后可以更改他們的配置文件。

    圖 1 顯示了訂閱表單。該示例應用程序不分發任何時事通訊,而實際的應用程序將向管理者、開發人員和管理員發送不同的時事通訊。(同一訂戶可能收到多個時事通訊。)該示例應用程序將用戶配置文件保存到關系數據庫中,并允許訂戶更改他們的配置文件。

    圖 1
    圖 1:“訂閱”窗體

    應用程序配置

    為使 JSF 控制您的 Web 表單,必須在 web.xml 應用程序描述文件中配置 Faces Servlet。您的 Web 頁面將具有 .jsp 擴展名,但 URL 將使用 .faces 后綴或 /faces/ 前綴。必須將 JSF 控制器 servlet 映射到 web.xml 文件中的 *.faces/faces/*。本文提供的示例應用程序使用后綴映射,并將 JSF 視圖保存到客戶端上:

    <web-app>
    ...
    <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
    </context-param>
    <servlet>
    <servlet-name>FacesServlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>FacesServlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
    </servlet-mapping>
    ...
    </web-app>
    

     

    模型視圖分離

     

    資源

     

    Oracle JDeveloper 10g (10.1.3) 開發人員預覽版為您提供了單一 IDE,通過它您除了可以編寫 Java 代碼以外,還可以定義 toplink 映射以及直觀構建 JSF 和 JSP 頁面。Oracle JDeveloper 還提供了由豐富的 JSF 組件組成的 ADF Faces 集,您可以在應用程序中重用這些組件。

    1) Oracle JDeveloper 10g (10.1.3) 開發人員預覽版下載

    2) 使用 JDeveloper 構建 JSF/TopLink

    3) 獲得 Oracle ADF Faces 組件的預先試用版:一組豐富的 JSF 組件。

    其他資源:

    了解如何結合使用 ADF Faces 和 JDeveloper 10g

    制作 Faces
    JavaServer Faces (JSF) 為創建 Web GUI 提供了一個強大的新框架。

    從 ADF UIX 到 JSF
    Oracle ADF Faces 組件為 JavaServer Faces 提供了一個可重用的資料庫。

    Oracle JDeveloper 和 JSF

    Java Web 應用程序(包括那些基于 JSF 的應用程序)使用 JSP 和 JavaBean 將表現形式與應用程序邏輯分離。使用類似 JSP 2.0 EL 的表達式語言 (EL) 可以將 JSF 標記的屬性綁定到 bean 屬性(有時為 bean 方法)。JSF EL 通常使用 #{...}語法(而非 ${...} 構造),以便用于 JSP 1.2 和 JSP 2.0。它還允許 JSF 在任何必要的情況下求解表達式(和重新求解),而不是讓 JSP 容器控制表達式的求解。在有意義的情況下,JSF EL 綁定是雙向的。例如,UI 組件可以取得 bean 屬性的值并將其作為默認值提供給用戶。當用戶提交表單數據時,此 UI 組件會更新 bean 屬性,以便應用程序邏輯可以處理該新值。

    JavaBean(模型)、JSF 頁面(視圖)和 Faces Servlet(控制器)具有定義良好的角色,從而分離了對象模型、表現方式和請求處理。您可以更深入一步,將 UI 特有的 Java 代碼與其實例維護和處理數據的類分離。一個可行的解決方案是構建對用戶界面一無所知的模型 bean,然后使用 JSF 特有的方法(如操作和驗證器,本文稍后將對其進行介紹)擴展這些模型類。

    該示例應用程序有兩個模型 bean(LoginInfoSubscriber),它們由兩個視圖 bean(LoginInfoBeanSubscriberBean)擴展,JSF 對這兩個視圖 bean 的實例進行管理。每個視圖 bean 實例都有一個標識符和一個作用域(您可以在表 1 中找到它們)。請注意,JSF 規范在提到視圖 bean 時使用了術語“托管 bean”(managed bean) 或“輔助 bean”(backing bean)。JSF 對“模型 bean”和“視圖 bean”不作區分。實際上,您可能將應用程序邏輯和 UI 代碼置于同一個類中,但這可能會降低類的可重用性,使維護變得更加困難,并且指定的開發人員無法將精力集中于他們的主要任務(如應用程序邏輯或 Web 設計)。

    模型 bean 類 視圖 bean 類 Bean 標識符 JSF 作用域
    LoginInfo LoginInfoBean loginInfo request
    Subscriber SubscriberBean subscriber session

    表 1:示例應用程序的模型 bean 和視圖 bean

     

    常規 JSP 頁面使用 <jsp:useBean> 標準操作實例化 JavaBean。使用 JSF 框架時,您不必在網頁中指定類名。而是在 XML 文件(通常名為 faces-config.xml)中配置 bean 實例。.(如果您開發大型應用程序,則可能使用多個配置文件。這種情況下,必須在 web.xml 文件中添加一個 javax.faces.CONFIG_FILES 參數。)對于每個 bean 實例,您必須指定標識符(bean 名)、類名和一個有效的 JSF 作用域(application、session、requestnone)。當在 JSF 表達式 (#{...}) 中引用 bean 時,JSF 框架將驗證該 bean 實例在給定作用域中是否存在,如果未找到,則創建它并使用默認值(這些默認值也可以在 JSF 配置文件中指定)對其進行初始化:

    <faces-config>
    ...
    <managed-bean>
    <managed-bean-name>loginInfo</managed-bean-name>
    <managed-bean-class>jsfdb.view.LoginInfoBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>
    <managed-bean>
    <managed-bean-name>subscriber</managed-bean-name>
    <managed-bean-class>jsfdb.view.SubscriberBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
    <property-name>email</property-name>
    <null-value/>
    </managed-property>
    ...
    <managed-property>
    <property-name>subscriptionType</property-name>
    <value>1</value>
    </managed-property>
    </managed-bean>
    ...
    </faces-config>
    

     

    由于可以將未聲明的屬性綁定到 JSF 頁面的 UI 組件,因此不必在 JSF 配置文件中將每個 bean 屬性聲明為托管屬性。僅當您要使用默認值初始化 bean 屬性時,才需要在 faces-config.xml 文件中指定它們。

    Bean 與頁面的關系

    數據訪問方法(在下一部分中介紹)從視圖 bean 的操作方法中調用。每個操作方法與 JSP 頁面中的一個提交按鈕綁定(如表 2 所示)。當用戶單擊該按鈕時,Web 瀏覽器將表單數據發送給服務器。JSF 框架驗證表單數據,并在出錯情況下將該表單返回給用戶。否則,則將有效的表單數據存儲到托管 bean 的屬性中,且 JSF 將調用綁定到被單擊按鈕的操作方法。因此,您的操作方法應該實現應用程序邏輯,以處理該 bean 的屬性。

    數據訪問方法 托管 bean 和 JSF 操作 JSF 頁面
    SubscriberDAO.select() LoginInfoBean.loginAction() login.jsp
    SubscriberDAO.insert() SubscriberBean.subscribeAction() subscribe.jsp
    SubscriberDAO.update() SubscriberBean.profileAction() profile.jsp
    SubscriberDAO.delete() SubscriberBean.unsubscribeAction() unsubscribe.jsp

    表 2:綁定到 JSF 頁面中的提交按鈕的 JSF 操作使用的 DAO 方法

     

    頁面到頁面的導航

    每個操作方法都返回一個名為“結果”的字符串。JSF 使用導航處理程序確定對每個結果所執行的操作。如果操作方法返回 null,則必須重新顯示同一頁面。否則,會根據返回的結果顯示其他頁面。表 3 包含該示例 Web 應用程序的 JSF 表單、可能的結果以及針對每個結果顯示的頁面。

    JSF 表單 可能的結果 顯示的頁面
    subscribe.jsp subscribed subscribed.jsp
    login.jsp profile profile.jsp
    login.jsp list list.jsp
    profile.jsp login login.jsp
    unsubscribe.jsp login login.jsp
    unsubscribe.jsp unsubscribed unsubscribed.jsp
    unsubscribe.jsp cancel profile.jsp

    表 3:當用戶單擊表單的提交按鈕時,JSF 將驗證表單數據,
    并在未出錯的情況下,調用綁定到被單擊按鈕的操作方法。然后,JSF 將使用該操作方法返回的結果
    確定接下來應顯示的頁面。

     

    默認 JSF 導航處理程序使用在 JSF 配置文件中指定的一組導航規則。例如,當用戶填寫登錄表單 (login.jsp) 并單擊 Login 按鈕時,loginAction() 方法會根據登錄用戶的角色(訂戶或管理員)返回 profilelist。請注意,如果用戶是未知的或者口令錯誤,則 loginAction() 將返回 null。這種情況下,JSF 將顯示同一登錄表單。如果身份驗證成功,則 JSF 將根據 loginAction() 返回的結果將該請求轉給 profile.jsplist.jsp。以下是 faces-config.xml 中規定的導航規則:

    <faces-config>
    ...
    <navigation-rule>
    <from-view-id>/login.jsp</from-view-id>
    <navigation-case>
    <from-outcome>profile</from-outcome>
    <to-view-id>/profile.jsp</to-view-id>
    </navigation-case>
    <navigation-case>
    <from-outcome>list</from-outcome>
    <to-view-id>/list.jsp</to-view-id>
    </navigation-case>
    </navigation-rule>
    ...
    </faces-config>
    

     

    JavaBean 和數據訪問對象 (DAO)

    本部分介紹了如何創建模型 bean 和視圖 bean,以及如何實現 bean 的持久性。模型 bean 定義必須保存到數據庫中的屬性。視圖 bean 使用 UI 特有的代碼(操作、驗證器等)擴展模型 bean。JSF 創建 faces-config.xml 文件中指定的視圖 bean 的實例,而該示例應用程序的持久層使用模型 bean。因此,該應用程序需要一個類似 ModelUtils.copy() 的實用方法,該方法將 JSF 實例化的視圖 bean 的屬性復制到持久層創建的模型對象,反之亦然。通過 ModelUtils 類,您還可以從名為 ModelResources 的資源包中獲取模型資源(如配置參數、SQL 語句和錯誤消息)。最后,ModelUtils 包含一個 getSubscriberDAO() 方法,該方法返回 SubscriberDAO 接口的一個實例,該接口定義了在關系數據庫中選擇、刪除、插入、更新 Subscriber 對象的方法。

    該示例應用程序提供了 SubscriberDAO 接口的兩個實現,一個基于 JDBC API,另一個使用 Oracle TopLink。這兩個實現不在同一應用程序實例中使用。ModelResources 包有一個 DAO 參數,用于指定要使用的 DAO 實現。基于 JDBC 的 DAO 的唯一好處是它只使用標準的 Java API 和 SQL。TopLink 也在內部使用 JDBC,但它提供了很多好處:

    • 由于使用可視化工具(Mapping Workbench 和 Session Editor)定義對象關系映射和配置應用程序,因此提高了工作效率。這顯著減少了手動編寫的代碼數量;
    • 不必再使用類似 JDBC 這樣的低級 API;
    • 因為 TopLink 緩存從數據庫讀取的對象,所以應用程序速度更快。此外,TopLink 在內部生成了非常高效的 SQL 語句;
    • 除 SQL 以外,TopLink 還支持幾種其他查詢機制,如“按示例查詢”、基于 Java 表達式的查詢和 EJB QL;
    • 對多服務器實例(集群)的支持使您能夠構建可伸縮的應用程序。

     

    創建模型 Bean 該示例 Web 應用程序包含兩個未使用任何 JSF API 的模型 bean(LoginInfoSubscriber)。LoginInfo 類定義了兩個屬性(emailpassword),并同任何 JavaBean 一樣,借助于 java.io.Serializable 接口聲明為可串行化:

    package jsfdb.model;
    public class LoginInfo implements java.io.Serializable {
    private String email;
    private String password;
    public String getEmail() {
    return email;
    }
    public void setEmail(String email) {
    this.email = email;
    }
    public String getPassword() {
    return password;
    }
    public void setPassword(String password) {
    this.password = password;
    }
    }
    

     

    Subscriber 類擴展了 LoginInfo、定義了五個其他屬性(name、manager、developer、administratorsubscriptionType),并有一個名為 countNewsletters() 的方法:

    package jsfdb.model;
    public class Subscriber extends LoginInfo {
    public static final int TYPE_DAILY = 1;
    public static final int TYPE_WEEKLY = 2;
    public static final int TYPE_MONTHLY = 3;
    private String name;
    private boolean manager;
    private boolean developer;
    private boolean administrator;
    private int subscriptionType;
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    ...
    public int countNewsletters() {
    int count = 0;
    if (isManager())
    count++;
    if (isDeveloper())
    count++;
    if (isAdministrator())
    count++;
    return count;
    }
    }
    

     

    Subscriber 的實例被保存到一個表中,可以使用以下 SQL 語句創建該表:

    CREATE TABLE subscribers (
    subscriberEmail VARCHAR2(80) PRIMARY KEY,
    subscriberPassword VARCHAR2(20),
    subscriberName VARCHAR2(80),
    managerFlag NUMBER(1),
    developerFlag NUMBER(1),
    administratorFlag NUMBER(1),
    subscriptionType NUMBER(1) )
    

     

    通常,該 bean 的屬性和相應的表列應具有相同的名稱。本文使用了不同的名稱,以便您可以輕松地辨認哪些地方使用了屬性、哪些地方使用了列。

    使用數據訪問對象SubscriberDAO 接口定義視圖 bean 為保存和檢索從模型 bean 繼承的屬性而調用的方法:

    package jsfdb.model.dao;
    import jsfdb.model.LoginInfo;
    import jsfdb.model.Subscriber;
    import jsfdb.model.err.IncorrectPasswordException;
    import jsfdb.model.err.LoginException;
    import jsfdb.model.err.ProfileException;
    import jsfdb.model.err.SubscribeException;
    import jsfdb.model.err.UnknownSubscriberException;
    import jsfdb.model.err.UnsubscribeException;
    public interface SubscriberDAO {
    public Subscriber select(LoginInfo loginInfo)
    throws LoginException,
    UnknownSubscriberException,
    IncorrectPasswordException;
    public void insert(Subscriber subscriber)
    throws SubscribeException;
    public void update(Subscriber subscriber)
    throws ProfileException;
    public void delete(Subscriber subscriber)
    throws UnsubscribeException;
    }
    

     

    getSubscriberDAO() 方法加載一個 DAO 實現(TopLinkSubscriberDAOJDBCSubscriberDAO)并返回所加載類的實例:

    package jsfdb.model;
    import jsfdb.model.dao.SubscriberDAO;
    ...
    public class ModelUtils {
    ...
    private static SubscriberDAO subscriberDAO;
    ...
    public synchronized static SubscriberDAO getSubscriberDAO() {
    if (subscriberDAO == null)
    try {
    Class daoClass = Class.forName(getResource("DAO"));
    subscriberDAO
    = (SubscriberDAO) daoClass.newInstance();
    } catch (Exception x) {
    log(x);
    throw new InternalError(x.getMessage());
    } catch (Exception x) {
    log(x);
    throw new InternalError(x.getMessage());
    } catch (Exception x) {
    log(x);
    throw new InternalError(x.getMessage());
    }
    return subscriberDAO;
    }
    ...
    }
    

     

    getSubscriberDAO() 方法使用 getResource()(使用 getResources() 獲取模型資源)獲取 DAO 實現的名稱。使用 log() 方法記錄任何意外錯誤:

    package jsfdb.model;
    ...
    import java.util.ResourceBundle;
    import java.util.MissingResourceException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    public class ModelUtils {
    public static final String RESOURCES
    = ModelUtils.class.getPackage().getName()
    + ".res.ModelResources";
    private static ResourceBundle resources;
    public static void log(Throwable x) {
    Logger.global.log(Level.SEVERE, x.getMessage(), x);
    }
    public static synchronized ResourceBundle getResources() {
    if (resources == null)
    try {
    resources = ResourceBundle.getBundle(RESOURCES);
    } catch (MissingResourceException x) {
    log(x);
    throw new InternalError(x.getMessage());
    }
    return resources;
    }
    public static String getResource(String key) {
    return getResources().getString(key);
    }
    ...
    }
    

     

    ModelResources 包包含 DAO 參數、基于 JDBC 的 DAO 使用的 SQL 語句以及由 DAO 方法拋出的異常消息:

    DAO=jsfdb.model.dao.TopLinkSubscriberDAO
    TopLinkSession=JSFDBSession
    # DAO=jsfdb.model.dao.JDBCSubscriberDAO
    JavaCompEnv=java:comp/env
    DataSource=jdbc/OracleDS
    SelectStatement=...
    InsertStatement=...
    UpdateStatement=...
    DeleteStatement=...
    SubscribeException=Subscription failed. \
    Please try another email address.
    ProfileException=Couln't update your profile. \
    Please contact the Webmaster.
    UnsubscribeException=Unsubscription failed. \
    Please contact the Webmaster.
    LoginException=Login failed. \
    Please contact the Webmaster.
    UnknownSubscriberException=Unknown subscriber. \
    Please subscribe.
    IncorrectPasswordException=Incorrect password. \
    Please try to login again.
    

     

    所有異常類均擴展了 ModelException:

    package jsfdb.model.err;
    import jsfdb.model.ModelUtils;
    public class ModelException extends Exception {
    public ModelException(String messageKey) {
    super(ModelUtils.getResource(messageKey));
    }
    }
    

     

    每個異常類只包含一個構造函數,該函數將類名傳遞給 ModelException 超類的構造函數:

    package jsfdb.model.err;
    public class LoginException extends ModelException {
    public LoginException() {
    super("LoginException");
    }
    }
    

     

    JDBCSubscriberDAO 類包含一個具有 JNDI 的 DataSource,并實現由 SubscriberDAO 定義的方法。請參閱本文附帶的可下載存檔中提供的 JDBCSubscriberDAO.java 源代碼。

    TopLinkSubscriberDAO 類也實現 SubscriberDAO 接口。TopLinkSubscriberDAO() 構造函數獲取一個服務器會話,并添加一個將在 JVM 停止時關閉會話的關閉鉤子 (hook)。基于 TopLink 的 DAO 包含用于獲取由實現 SubscriberDAO 接口的方法使用的客戶端會話和工作單元的實用方法:

    package jsfdb.model.dao;
    ...
    import oracle.toplink.sessions.UnitOfWork;
    import oracle.toplink.threetier.ClientSession;
    import oracle.toplink.threetier.Server;
    import oracle.toplink.tools.sessionmanagement.SessionManager;
    public class TopLinkSubscriberDAO implements SubscriberDAO {
    private Server serverSession;
    public TopLinkSubscriberDAO() {
    SessionManager manager = SessionManager.getManager();
    String id = ModelUtils.getResource("TopLinkSession");
    ClassLoader loader = this.getClass().getClassLoader();
    serverSession = (Server) manager.getSession(id, loader);
    Runtime.getRuntime().addShutdownHook(new Thread() {
    public void run() {
    serverSession.logout();
    SessionManager.getManager().getSessions().remove(
    ModelUtils.getResource("TopLinkSession"));
    }
    });
    }
    private ClientSession acquireClientSession() {
    return serverSession.acquireClientSession();
    }
    private UnitOfWork acquireUnitOfWork() {
    return acquireClientSession().acquireUnitOfWork();
    }
    ...
    }
    

     

    創建視圖 Bean 如前所述,這兩個視圖 bean(LoginInfoBean 和 SubscriberBean)使用 UI 特有的代碼(如用于驗證數據的方法(驗證器)和用于處理數據的方法(操作))擴展了模型 bean(LoginInfoSubscriber)。LoginInfoBean 類包含一個名為 loginAction() 的操作方法。另一個視圖 bean (SubscriberBean) 定義另一個屬性 (loggedIn),實現 emailValidator() 方法(用于驗證電子郵件地址中是否存在字符 @)、提供幾個操作方法,并將從 Subscriber 繼承的常量作為只讀屬性公開,以便可以在 JSF 頁面中使用 EL 訪問它們:

    package jsfdb.view;
    import jsfdb.model.Subscriber;
    ...
    public class SubscriberBean extends Subscriber {
    ...
    private transient boolean loggedIn = false;
    public boolean isLoggedIn() {
    return loggedIn;
    }
    public void setLoggedIn(boolean loggedIn) {
    this.loggedIn = loggedIn;
    }
    public void emailValidator(FacesContext context,
    UIComponent comp, Object value) {
    ...
    }
    public String subscribeAction() {
    ...
    }
    public String profileAction() {
    ...
    }
    public String unsubscribeAction() {
    ...
    }
    public String cancelAction() {
    if (!loggedIn)
    return "login";
    else
    return "cancel";
    }
    public int getDailyConst() {
    return TYPE_DAILY;
    }
    public int getWeeklyConst() {
    return TYPE_WEEKLY;
    }
    public int getMonthlyConst() {
    return TYPE_MONTHLY;
    }
    }
    

     

    本部分的下列段落將詳細介紹這兩個視圖 bean 的操作方法。具體說來,您將了解

    • loginAction() 如何選擇行,
    • subscribeAction() 如何插入新行,
    • profileAction() 如何更新現有行,
    • unsubscribeAction() 如何刪除行。

     

    下一部分(JSF 視圖和數據驗證

    • 提供該示例應用程序的 JSF 頁面,
    • 介紹了如何借助 JSF 對數據進行驗證,
    • 演示了如何創建 UI 組件和 bean 屬性之間的綁定,
    • 演示了如何將操作方法綁定到 JSF 表單的提交按鈕。

     

    選擇行(登錄操作)JDBCSubscriberDAOselect() 方法執行一個 SQL 查詢,選擇具有給定電子郵件地址的訂戶,然后驗證口令。以下是基于 JDBC 的 DAO 使用的 SELECT 語句:

    SELECT subscriberPassword,
    subscriberName,
    managerFlag,
    developerFlag,
    administratorFlag,
    subscriptionType
    FROM subscribers
    WHERE subscriberEmail=?
    

     

    使用 TopLink 時,盡管您可以使用 SQL,但不必構建 SQL 語句。如果在私有 read() 方法中使用查詢 API(它從 TopLinkSubscriberDAO 的多個公共方法中調用,執行一個返回具有給定電子郵件地址的訂戶的查詢),則 TopLink 框架將生成 SQL 查詢:

    package jsfdb.model.dao;
    ...
    import oracle.toplink.expressions.ExpressionBuilder;
    import oracle.toplink.queryframework.ReadObjectQuery;
    import oracle.toplink.sessions.Session;
    public class TopLinkSubscriberDAO implements SubscriberDAO {
    ...
    private Subscriber read(Session session, String email) {
    ReadObjectQuery query
    = new ReadObjectQuery(Subscriber.class);
    ExpressionBuilder builder = new ExpressionBuilder();
    query.setSelectionCriteria(
    builder.get("email").equal(email));
    return (Subscriber) session.executeQuery(query);
    }
    ...
    }
    

     

    TopLinkSubscriberDAOselect() 方法取得客戶端會話并調用 read()

    package jsfdb.model.dao;
    ...
    public class TopLinkSubscriberDAO implements SubscriberDAO {
    ...
    public Subscriber select(LoginInfo loginInfo)
    throws LoginException,
    UnknownSubscriberException,
    IncorrectPasswordException {
    Subscriber s = null;
    try {
    ClientSession session = acquireClientSession();
    s = read(session, loginInfo.getEmail());
    } catch (Exception x) {
    ModelUtils.log(x);
    throw new LoginException();
    }
    if (s == null)
    throw new UnknownSubscriberException();
    if (!s.getPassword().equals(loginInfo.getPassword()))
    throw new IncorrectPasswordException();
    return s;
    }
    ...
    }
    

     

    LoginInfoBean 類包含 loginAction() 方法,該方法使用 ViewUtils.eval() 獲取 SubscriberBean 類(由 JSF 管理)的實例,并獲取 adminEmail 初始化參數的值。隨后,loginAction() 調用 select(),使用 ModelUtils.copy() 將選定訂戶的屬性復制到由 JSF 管理的 JavaBean,將 loggedIn 標記設置為 true,然后根據訂戶的電子郵件返回 listprofile 結果:

    package jsfdb.view;
    import jsfdb.model.LoginInfo;
    import jsfdb.model.Subscriber;
    import jsfdb.model.ModelUtils;
    import jsfdb.model.err.LoginException;
    import jsfdb.model.err.IncorrectPasswordException;
    import jsfdb.model.err.UnknownSubscriberException;
    public class LoginInfoBean extends LoginInfo {
    public String loginAction() {
    SubscriberBean subscriber
    = (SubscriberBean) ViewUtils.eval("#{subscriber}");
    String adminEmail
    = (String) ViewUtils.eval("#{initParam.adminEmail}");
    try {
    Subscriber selectedSubscriber
    = ModelUtils.getSubscriberDAO().select(this);
    ModelUtils.copy(selectedSubscriber, subscriber);
    subscriber.setLoggedIn(true);
    if (subscriber.getEmail().equals(adminEmail))
    return "list";
    else
    return "profile";
    } catch (LoginException x) {
    ViewUtils.addExceptionMessage(x);
    return null;
    } catch (UnknownSubscriberException x) {
    ViewUtils.addExceptionMessage(x);
    return null;
    } catch (IncorrectPasswordException x) {
    ViewUtils.addExceptionMessage(x);
    return null;
    }
    }
    }
    

     

    ViewUtils 類提供用于求解 JSF 表達式和將錯誤消息添加到 JSF 上下文的實用方法:

    package jsfdb.view;
    import javax.faces.application.FacesMessage;
    import javax.faces.context.FacesContext;
    import javax.faces.el.ValueBinding;
    import java.util.ResourceBundle;
    public class ViewUtils {
    public static Object eval(String expr) {
    FacesContext context
    = FacesContext.getCurrentInstance();
    ValueBinding binding
    = context.getApplication().createValueBinding(expr);
    return binding.getValue(context);
    }
    public static void addErrorMessage(FacesContext context,
    String compId, String messageId) {
    ResourceBundle bundle = ResourceBundle.getBundle(
    context.getApplication().getMessageBundle());
    FacesMessage message = new FacesMessage(
    bundle.getString(messageId));
    message.setSeverity(FacesMessage.SEVERITY_ERROR);
    context.addMessage(compId, message);
    }
    public static void addExceptionMessage(Exception x) {
    FacesContext context
    = FacesContext.getCurrentInstance();
    FacesMessage message
    = new FacesMessage(x.getMessage());
    message.setSeverity(FacesMessage.SEVERITY_FATAL);
    context.addMessage(null, message);
    }
    }
    

     

    ModelUtilscopy() 方法獲取源 bean 的屬性,并使用 JavaBean Introspection API 和 Java Reflection API 設置另一個 bean 的屬性:

    package jsfdb.model;
    ...
    import java.beans.BeanInfo;
    import java.beans.Introspector;
    import java.beans.PropertyDescriptor;
    import java.beans.IntrospectionException;
    import java.lang.reflect.Method;
    import java.lang.reflect.InvocationTargetException;
    ...
    public class ModelUtils {
    ...
    public static void copy(Object source, Object dest) {
    try {
    Class sourceClass = source.getClass();
    Class destClass = dest.getClass();
    BeanInfo info = Introspector.getBeanInfo(sourceClass);
    PropertyDescriptor props[]
    = info.getPropertyDescriptors();
    Object noParams[] = new Object[0];
    Object oneParam[] = new Object[1];
    for (int i = 0; i < props.length; i++) {
    Method getter = props[i].getReadMethod();
    if (getter == null)
    continue;
    Object value = getter.invoke(source, noParams);
    Method setter = props[i].getWriteMethod();
    if (setter != null && sourceClass != destClass)
    try {
    setter = destClass.getMethod(
    setter.getName(),
    setter.getParameterTypes());
    } catch (NoSuchMethodException x) {
    setter = null;
    }
    if (setter != null) {
    oneParam[0] = value;
    setter.invoke(dest, oneParam);
    }
    }
    } catch (IntrospectionException x) {
    log(x);
    throw new InternalError(x.getMessage());
    } catch (IllegalAccessException x) {
    log(x);
    throw new InternalError(x.getMessage());
    } catch (IllegalArgumentException x) {
    log(x);
    throw new InternalError(x.getMessage());
    } catch (SecurityException x) {
    log(x);
    throw new InternalError(x.getMessage());
    } catch (InvocationTargetException x) {
    log(x.getTargetException());
    throw new InternalError(
    x.getTargetException().getMessage());
    }
    }
    }
    

     

    如果所有屬性都是基元對象或不變對象(如 String),則 copy() 方法將比較適用。如果某個 bean 包含可變對象(如子 bean 或集合),則應克隆這些屬性的值并將它們的克隆存儲到 dest bean 中。

    插入新行(訂閱操作)JDBCSubscriberDAOinsert() 方法執行一個 SQL 語句,向數據庫中插入一個新訂戶:

    INSERT INTO subscribers (
    subscriberEmail,
    subscriberPassword,
    subscriberName,
    managerFlag,
    developerFlag,
    administratorFlag,
    subscriptionType )
    VALUES (?, ?, ?, ?, ?, ?, ?)
    

     

    使用 TopLink 時,在必須使用 TopLink Mapping Workbench 工具定義的對象關系映射中將自動生成 SQL 語句。TopLinkSubscriberDAOinsert() 方法獲取一個工作單元、創建一個新的 Subscriber 實例、設置它的屬性、注冊新對象并調用 commit()

    package jsfdb.model.dao;
    ...
    public class TopLinkSubscriberDAO implements SubscriberDAO {
    ...
    public void insert(Subscriber subscriber)
    throws SubscribeException {
    try {
    UnitOfWork uow = acquireUnitOfWork();
    Subscriber s = new Subscriber();
    ModelUtils.copy(subscriber, s);
    uow.registerObject(s);
    uow.commit();
    } catch (Exception x) {
    ModelUtils.log(x);
    throw new SubscribeException();
    }
    }
    ...
    }
    

     

    SubscriberBeansubscribeAction() 方法調用 insert()、將 loggedIn 標記設置為 true,并在未出錯的情況下返回 subscribed 結果:

    package jsfdb.view;
    ...
    public class SubscriberBean extends Subscriber {
    ...
    public final static String SELECT_NEWSLETTER_ID
    = "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
    ...
    public String subscribeAction() {
    if (countNewsletters() == 0) {
    ViewUtils.addErrorMessage(
    FacesContext.getCurrentInstance(),
    null, SELECT_NEWSLETTER_ID);
    return null;
    }
    try {
    ModelUtils.getSubscriberDAO().insert(this);
    setLoggedIn(true);
    return "subscribed";
    } catch (SubscribeException x) {
    ViewUtils.addExceptionMessage(x);
    return null;
    }
    }
    ...
    }
    

     

    更新現有行(配置文件操作)JDBCSubscriberDAOupdate() 方法執行一個 SQL 語句,更新現有訂戶的配置文件:

    UPDATE subscribers SET
    subscriberPassword=?,
    subscriberName=?,
    managerFlag=?,
    developerFlag=?,
    administratorFlag=?,
    subscriptionType=?
    WHERE subscriberEmail=?
    

     

    TopLinkSubscriberDAOupdate() 方法獲取一個工作單元、調用私有 read() 方法獲取具有給定電子郵件的 bean、更新 bean 屬性并調用 commit()

    package jsfdb.model.dao;
    ...
    public class TopLinkSubscriberDAO implements SubscriberDAO {
    ...
    public void update(Subscriber subscriber)
    throws ProfileException {
    try {
    UnitOfWork uow = acquireUnitOfWork();
    Subscriber s = read(uow, subscriber.getEmail());
    ModelUtils.copy(subscriber, s);
    uow.commit();
    } catch (Exception x) {
    ModelUtils.log(x);
    throw new ProfileException();
    }
    }
    ...
    }
    

     

    SubscriberBeanprofileAction() 方法調用 update() 并返回 null 結果:

    package jsfdb.view;
    ...
    public class SubscriberBean extends Subscriber {
    ...
    public final static String SELECT_NEWSLETTER_ID
    = "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
    ...
    public String profileAction() {
    if (!loggedIn)
    return "login";
    if (countNewsletters() == 0) {
    ViewUtils.addErrorMessage(
    FacesContext.getCurrentInstance(),
    null, SELECT_NEWSLETTER_ID);
    return null;
    }
    try {
    ModelUtils.getSubscriberDAO().update(this);
    return null;
    } catch (ProfileException x) {
    ViewUtils.addExceptionMessage(x);
    return null;
    }
    }
    ...
    }
    

     

    刪除行(取消訂閱操作)JDBCSubscriberDAOdelete() 方法執行一個 SQL 語句,從數據庫中刪除一個訂戶:

    DELETE FROM subscribers WHERE subscriberEmail=?
    

     

    TopLinkSubscriberDAOdelete() 方法獲取一個工作單元、調用私有 read() 方法獲取具有給定電子郵件的 bean、刪除 bean 屬性并調用 commit()

    package jsfdb.model.dao;
    ...
    public class TopLinkSubscriberDAO implements SubscriberDAO {
    ...
    public void delete(Subscriber subscriber)
    throws UnsubscribeException {
    try {
    UnitOfWork uow = acquireUnitOfWork();
    Subscriber s = read(uow, subscriber.getEmail());
    uow.deleteObject(s);
    uow.commit();
    } catch (Exception x) {
    ModelUtils.log(x);
    throw new UnsubscribeException();
    }
    }
    }
    

     

    SubscriberBeanunsubscribeAction() 方法調用 delete() 并在未出錯的情況下返回 unsubscribed 結果:

    package jsfdb.view;
    ...
    public class SubscriberBean extends Subscriber {
    ...
    public String unsubscribeAction() {
    if (!loggedIn)
    return "login";
    try {
    ModelUtils.getSubscriberDAO().delete(this);
    return "unsubscribed";
    } catch (UnsubscribeException x) {
    ViewUtils.addExceptionMessage(x);
    return null;
    }
    }
    ...
    }
    

     

    創建 TopLink 項目以下步驟說明了如何使用 Mapping Workbench 創建一個 TopLink 項目。

    第 1 步:啟動 OracleAS TopLink Mapping Workbench。然后,從主菜單中選擇 FileNew Project...。在 Create New Project 對話框中,提供現有數據庫的名稱(例如,orcl),然后單擊 OK

    第 2 步:Mapping Workbench 將顯示一個窗口讓您保存該項目。選擇一個目錄,提供一個文件名(例如,jsfdb),然后單擊 Save

    第 3 步:在左側 Navigator 窗格中選擇該數據庫,單擊 Add...,輸入一個登錄標簽(例如,orclLogin),然后單擊 OK

    第 4 步:在主窗口中,選擇新建的登錄,并提供 JDBC 驅動程序類的名稱、數據庫 URL、用戶名和口令。別忘了選中 Save Password 復選框。

    圖 2

    第 5 步:Navigator 窗格中右鍵單擊該數據庫名稱,然后單擊 Log In to Database

    第 6 步:Navigator 窗格中再次右鍵單擊該數據庫名稱,然后單擊 Add or Update Existing Tables from Database

    第 7 步:Import Tables from Database 窗口中的 Table Name Pattern 域中,輸入 SUBSCRIBERS,單擊 Get Table Names,從左側 Available Tables 窗格中選擇 SUBSCRIBERS 表,將它添加到右側的 Selected Tables 窗格,然后單擊 OK

    圖 3

    第 8 步:Navigator 窗格中選擇該項目 (jsfdb),確保 General 是右側窗格的當前選項卡,單擊 Add Entries...,在顯示的目錄樹中尋找,選擇包含該 Web 應用程序已編譯的類的目錄,然后單擊 OK。選中的目錄將被添加到主窗口的 Class Path 列表中。

    第 9 步:Navigator 窗格中再次右鍵單擊該項目,然后單擊 Add or Refresh Classes...

    第 10 步:Select Classes 窗口左側的 Available Packages/Classes 窗格中選擇 LoginInfoSubscriber 類。

    第 11 步:LoginInfoSubscriber 類移到右側的 Selected Classes 窗格中,然后單擊 OK。只有 Subscriber 類被映射到了表上,但由于 LoginInfoSubscriber 的超類,因此我們還需要將其映射到表上。

    圖 4

    第 12 步:在主窗口的 Navigator 窗格中選擇 Subscriber 類。在右側的 Editor 窗格中,確保 Descriptor 是當前選項卡,并在 Associated Table 列表中選擇 SUBSCRIBERS

    圖 5

    第 13 步:Navigator 窗格中,右鍵單擊 Subscriber 類,選擇 Map Inherited Attributes 并單擊 To Superclass

    第 14 步:右鍵單擊 Subscriber 類的每個 bean 屬性(例如,administrator),選擇 Map As...,然后單擊 Direct to Field

    第 15 步:Navigator 窗格中選擇 Subscriber 類的每個 bean 屬性(例如,email),然后在右側的 Editor 窗格中設置相應的 Database Field(例如,SUBSCRIBEREMAIL)。

    圖 6

    第 16 步:由于 LoginInfo 類未映射到任何表,因此必須從項目中刪除它。在 Navigator 窗格中右鍵單擊 LoginInfo,然后單擊 Remove Class。當顯示 Remove Descriptors 對話框時,單擊 Remove

    第 17 步:在主菜單中,選擇 File、Export、Project Deployment XML... 或使用 CTRL+D。輸入 JSFDBProject.xml 并單擊 OK。然后,選擇要將該 XML 文件保存到的目錄。

    圖 8

     

     

    第 18 步:使用 FileSave 或使用 CTRL+S 保存該項目。除了將項目設置存儲到 jsfdb.mwp 文件中以外,保存操作還將在 class、descriptortable 子目錄中創建幾個 XML 文件。現在,您可以選擇 OracleAS TopLink Mapping Workbench 了。

    配置 TopLink 會話下列步驟說明了如何使用 Sessions Editor 配置一個 TopLink 會話。

    第 1 步:啟動 OracleAS TopLink Sessions Editor。從主菜單中選擇 FileNew...(或使用 CTRL+N)。在 New 對話框中,保持 sessions.xml 文件名不變,單擊 Browse 按鈕為該文件選擇 Location,在 Session Name 中輸入 JSFDBSession,然后單擊 OK

    第 2 步:Navigator 窗格中選擇 JSFDBSession,確保 GeneralEditor 窗格的當前選項卡,然后在 Project Type 組的 XML 域中輸入 JSFDBProject.xml

    圖 7

    第 3 步:選擇 Editor 窗格的 Logging 選項卡,然后在Enable Logging 列表中選擇 True

    第 4 步:選擇 Editor 窗格的 Login 選項卡,選中 Database Platform 復選框,在相應的列表中選擇 Oracle,然后在 Data Source 域中輸入 java:comp/env/jdbc/OracleDS

    圖 9

    第 5 步:從主菜單中選擇 FileSave(或使用 CTRL+S)保存 sessions.xml 文件。現在,您可以關閉 OracleAS TopLink Sessions Editor 了。

    JSF 視圖和數據驗證

    該示例 Web 應用程序包含一個訂閱頁面 (subscribe.jsp)、一個登錄頁面 (login.jsp)、一個配置文件編輯頁面 (profile.jsp)、一個取消訂閱頁面 (unsubscribe.jsp、兩個確認頁面(subscribed.jspunsubscribed.jsp)、一個列出所有訂戶的頁面 (list.jsp) 和一個注銷頁面 (logout.jsp)。由 JSF 頁面創建的某些表單借助于先前部分中的 DAO 方法觸發執行基本數據庫操作(INSERT、SELECT、UPDATEDELETE)的操作。

    在網頁中使用 JSFJSF 定義了兩個標準標記庫(Core 和 HTML),您必須在 JSP 頁面中使用 <%@taglib%> 指令聲明它們。JSF Core 庫包含不依賴任何標記語言的標記,而 JSF HTML 庫是為在 Web 瀏覽器中查看的頁面設計的。這兩個標記庫的標準前綴分別是 f(用于 JSF Core)和 h(用于 JSF HTML)。所有 JSF 標記必須嵌套在 <f:view> 元素的內部。通過 <f:view> 標記,JSF 框架可以將 UI 組件的狀態作為 HTTP 請求響應的一部分保存。

    該示例 Web 應用程序的頁面使用 JSF 和 JSTL Core 庫創建 HTML 頭:

    <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
    <%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
    <%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
    <f:view>
    <f:loadBundle var="labels" basename="jsfdb.view.res.Labels"/>
    <c:set var="stylesheet"
    value="${pageContext.request.contextPath}/stylesheet.css"/>
    <html>
    <head>
    <title><h:outputText value="#{labels.subscribe}"/></title>
    <link rel="stylesheet" type="text/css"
    href="<c:out value='${stylesheet}'/>">
    </head>
    <body>
    ...
    </body>
    </html>
    </f:view>
    

     

    JSF 的 <h:outputText> 標記與 JSTL 的 <c:out> 標記非常相似,但也有一些差別。例如,<h:outputText> 使用 JSF EL,而 <c:out> 使用最初為 JSTL 1.0 創建并在隨后被 JSP 2.0 采用的表達式語言。在上一個代碼段中,<f:loadBundle> 標記加載了一個包含所有 JSF 頁面的標簽和標題的資源包:

    subscribe=Subscribe
    subscribed=Subscribed
    login=Login
    logout=Logout
    profile=Profile
    update=Update
    unsubscribe=Unsubscribe
    unsubscribed=Unsubscribed
    cancel=Cancel
    email=Email
    password=Password
    passwordDetail=Useful to change your profile
    name=Name
    newsletters=Newsletters
    manager=Manager
    developer=Developer
    administrator=Administrator
    subscriptionType=Subscription Type
    daily=Daily
    weekly=Weekly
    monthly=Monthly
    list=List
    

     

    使用 JSF 標記構建表單許多應用程序的網頁使用 JSF 創建表單。例如,login.jsp 使用 <h:inputText><h:inputSecret>,它們呈現兩個類型分別為 textpassword 的 HTML <input> 元素。每個表單都有一個使用 <h:outputLabel> 呈現的標簽。所有錯誤消息都使用 <h:message><h:messages> 呈現。<f:validateLength> 標記驗證用戶在表單域中輸入的字符串的長度。<h:commandButton> 呈現一個 HTML 提交按鈕。所有這些標記都位于 <h:form> 元素(呈現 HTML <form></form> 標記)中。除了 HTML 表單以外,login.jsp 頁面還提供了訂閱表單的鏈接,并使用 <h:outputLink><h:outputText> 呈現 HTML <a> 元素及其內容:

    <!-- login.jsp -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
    <%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
    <%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
    <c:remove var="subscriber" scope="session"/>
    <f:view>
    ...
    <h1><h:outputText value="#{labels.login}"/></h1>
    <h:outputLink value="subscribe.faces">
    <h:outputText value="#{labels.subscribe}"/>
    </h:outputLink>
    <h:form id="login">
    <h:messages globalOnly="true" styleClass="message"/>
    <p><h:outputLabel for="email"
    value="#{labels.email}"/>
    <h:message for="email" styleClass="message"/><br>
    <h:inputText id="email" required="true"
    value="#{loginInfo.email}"
    size="40" maxlength="80">
    <f:validateLength minimum="1" maximum="80"/>
    </h:inputText>
    <p><h:outputLabel for="password"
    value="#{labels.password}"/>
    <h:message for="password" styleClass="message"/><br>
    <h:inputSecret id="password" required="true"
    value="#{loginInfo.password}"
    size="10" maxlength="20">
    <f:validateLength minimum="6" maximum="20"/>
    </h:inputSecret>
    <p><h:commandButton id="command"
    value="#{labels.login}"
    action="#{loginInfo.loginAction}"/>
    </h:form>
    ...
    </f:view>
    

     

    當執行 login.jsp 頁面時,JSF 創建 LoginInfoBean 實例,該實例的屬性綁定到 UI 組件。當單擊 Login 按鈕時,Web 瀏覽器將用戶的電子郵件地址和口令提交給服務器。JSF 框架驗證用戶是否為這兩個必填域提供了值。如果字符串值的長度通過了 <f:validateLength> 執行的驗證,則 JSF 設置 bean 屬性并調用 loginAction() 方法對用戶進行身份驗證。

    請注意,login.jsp 使用 JSTL 的 <c:remove> 標記從 session 作用域中刪除任何 subscriber bean。如果用戶首次訪問該 Web 應用程序,或者如果他的前一個會話已經過期,則不存在這樣的 bean 實例,因此 <c:remove> 將不起作用。否則,該標記清理該會話作用域。由于在 loginAction() 中求解的一個 EL 表達式中引用了 subscriber bean,因此 JSF 將創建 SubscriberBean(在 faces-config.xml 文件中指定)的實例。loginAction() 方法從數據庫中讀取訂戶的配置文件并設置該 bean 的屬性。subscriber bean 將由其他 JSF 頁面使用,并將保存在 session 作用域中,直到 logout.jsp 刪除它:

    <!-- logout.jsp -->
    ...
    <f:view>
    ...
    </f:view>
    <c:remove var="subscriber" scope="session"/>
    

     

    subscribe.jspprofile.jsp 頁面使用 JSF 呈現其他表單元素,如復選框和列表。JSF 的 <h:panelGrid><h:panelGroup> 標記用于呈現其單元格包含訂閱表單的三個復選框的 HTML 表格。<h:selectBooleanCheckbox> 標記呈現 checkbox 類型的 <input> 元素

    <!-- subscribe.jsp -->
    ...
    <f:view>
    ...
    <h:form id="subscribe">
    ...
    <p><h:outputText value="#{labels.newsletters}"/>
    <h:message for="newsletters" styleClass="message"/><br>
    <h:panelGrid id="newsletters"
    columns="3" border="0" cellspacing="5">
    <h:panelGroup>
    <h:selectBooleanCheckbox id="manager"
    value="#{subscriber.manager}"/>
    <h:outputLabel for="manager"
    value="#{labels.manager}"/>
    </h:panelGroup>
    <h:panelGroup>
    <h:selectBooleanCheckbox id="developer"
    value="#{subscriber.developer}"/>
    <h:outputLabel for="developer"
    value="#{labels.developer}"/>
    </h:panelGroup>
    <h:panelGroup>
    <h:selectBooleanCheckbox id="administrator"
    value="#{subscriber.administrator}"/>
    <h:outputLabel for="administrator"
    value="#{labels.administrator}"/>
    </h:panelGroup>
    </h:panelGrid>
    ...
    </h:form>
    ...
    </f:view>
    

     

    <h:selectOneMenu><f:selectItem> 標記呈現一個下拉式列表,用戶可以通過該列表選擇他們的訂閱類型:每日、每周或每月:

    <!-- subscribe.jsp -->
    ...
    <f:view>
    ...
    <h:form id="subscribe">
    ...
    <p><h:outputLabel for="subscriptionType"
    value="#{labels.subscriptionType}"/>
    <h:message for="subscriptionType"
    styleClass="message"/><br>
    <h:selectOneMenu id="subscriptionType"
    value="#{subscriber.subscriptionType}"
    required="true">
    <f:validateLongRange minimum="1" maximum="3"/>
    <f:selectItem itemLabel="#{labels.daily}"
    itemValue="#{subscriber.dailyConst}"/>
    <f:selectItem itemLabel="#{labels.weekly}"
    itemValue="#{subscriber.weeklyConst}"/>
    <f:selectItem itemLabel="#{labels.monthly}"
    itemValue="#{subscriber.monthlyConst}"/>
    </h:selectOneMenu>
    ...
    </h:form>
    ...
    </f:view>
    

     

    subscribeAction() 方法(在 subscribe.jsp 中使用)將新訂戶的配置文件保存到數據庫中。profile.jsp 頁面顯示由 subscriber bean 保持的首選設置,并允許用戶更改他的配置文件(profileAction() 方法更新存儲在數據庫中的信息)。

    unsubscribe.jsp 呈現的表單包含兩個綁定到不同操作方法的提交按鈕:

    <!-- unsubscribe.jsp -->
    ...
    <f:view>
    ...
    <h:form id="unsubscribe">
    ...
    <p><h:commandButton id="command"
    value="#{labels.unsubscribe}"
    action="#{subscriber.unsubscribeAction}"/>
    <h:commandButton id="cancel"
    value="#{labels.cancel}"
    action="#{subscriber.cancelAction}"/>
    </h:form>
    ...
    </f:view>
    

     

    unsubscribeAction() 方法從數據庫中刪除訂戶的配置文件。先前部分(JavaBean 和數據訪問對象)介紹了所有操作方法的代碼。

    使用標準的和定制的驗證器 JSF 提供了幾個驗證器標記,如在示例應用程序的 JSF 頁面中使用的 <f:validateLength><f:validateLongRange>。此外,許多 HTML 標記支持 required 屬性。通過該屬性可以指定用戶是否必須總要為該 UI 組件提供值。但在大多數情況下,您需要應用程序特有的驗證。您可以構建定制驗證標記,但 validator 屬性(許多標準 JSF 標記都支持它)更容易使用。您可以使用該屬性指定一個將用于驗證用戶輸入值的方法:

    <!-- subscribe.jsp -->
    ...
    <f:view>
    ...
    <h:form id="subscribe">
    ...
    <p><h:outputLabel for="email"
    value="#{labels.email}"/>
    <h:message for="email" styleClass="message"/><br>
    <h:inputText id="email" required="true"
    validator="#{subscriber.emailValidator}"
    value="#{subscriber.email}"
    size="40" maxlength="80">
    <f:validateLength minimum="1" maximum="80"/>
    </h:inputText>
    ...
    </h:form>
    ...
    </f:view>
    

     

    SubscriberBeanemailValidator() 方法驗證電子郵件地址是否包含 @ 字符:

    package jsfdb.view;
    ...
    import javax.faces.component.UIComponent;
    import javax.faces.component.EditableValueHolder;
    import javax.faces.context.FacesContext;
    public class SubscriberBean extends Subscriber {
    public final static String INVALID_EMAIL_ID
    = "jsfdb.view.SubscriberBean.INVALID_EMAIL";
    ...
    public void emailValidator(FacesContext context,
    UIComponent comp, Object value) {
    String email = (String) value;
    if (email.indexOf("@") == -1) {
    String compId = comp.getClientId(context);
    ViewUtils.addErrorMessage(
    context, compId, INVALID_EMAIL_ID);
    ((EditableValueHolder) comp).setValid(false);
    }
    }
    ...
    }
    

     

    錯誤消息通過 <h:message/> 在 JSF 頁面中呈現,并從應用程序消息包中被獲取:

    jsfdb.view.SubscriberBean.INVALID_EMAIL=invalid
    jsfdb.view.SubscriberBean.SELECT_NEWSLETTER=\
    You must subscribe to at least one newsletter.
    javax.faces.component.UIInput.REQUIRED=required
    javax.faces.validator.LengthValidator.MAXIMUM=\
    may contain maximum {0} characters
    javax.faces.validator.LengthValidator.MINIMUM=\
    must contain minimum {0} characters
    

     

    除了應用程序特有的消息外,該資源包還包含幾個默認 JSF 消息的替代形式。例如,javax.faces.component.UIInput.REQUIRED 關鍵字之后是一個錯誤消息,它將在用戶沒有為必填表單元素提供值時呈現。必須在 faces-config.xml 中配置應用程序消息包:

    <faces-config>
    <application>
    <locale-config>
    <default-locale>en</default-locale>
    </locale-config>
    <message-bundle>jsfdb.view.res.Messages</message-bundle>
    </application>
    ...
    </faces-config>
    

     

    驗證器標記和方法驗證單個 UI 組件的值。但有時您不得不一起驗證一組組件。這可以在一個操作方法中完成。例如,用戶可能沒有選中訂閱表單的任何復選框,但至少應訂閱一種時事通訊。如果 countNewsletters() 返回 0,則 subscribeAction() 方法將發出錯誤消息:

    package jsfdb.view;
    ...
    public class SubscriberBean extends Subscriber {
    ...
    public final static String SELECT_NEWSLETTER_ID
    = "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
    ...
    public String subscribeAction() {
    if (countNewsletters() == 0) {
    ViewUtils.addErrorMessage(
    FacesContext.getCurrentInstance(),
    null, SELECT_NEWSLETTER_ID);
    return null;
    }
    ...
    }
    ...
    }
    

     

    未關聯到任何特定 UI 組件的錯誤消息可以在 JSF 頁面中使用 <h:messages globalOnly="true"/> 呈現。

    結合使用 JSF 與 JSTL 的 SQL 標記 JSF API 為表定義了一個抽象數據模型 (javax.faces.model.DataModel),并提供了用于包裝數組、列表、JDBC 結果集、JSTL 的 <sql:query> 標記的結果以及標量的實現。JSF 數據模型包含用于訪問行數據的方法,但行的表示方式由具體的模型實現決定。當使用數組或列表時,每個元素都是一行。當使用結果集時,每一行都表示為一個其關鍵字為表列的 java.util.Map。由于不限制行模型,因此 Web 開發人員應負責使用 <h:column> 標記(必須嵌套在 <h:dataTable> 元素的內部)定義列在 JSF 頁面中的呈現方式。

    該示例應用程序的 web.xml 文件使用 <resource-ref> 聲明一個數據源,并使用一個名為 javax.servlet.jsp.jstl.sql.dataSource 的上下文參數將它配置為默認的 JSTL 數據源。另一個參數 (adminEmail) 指定一個電子郵件地址,用于與登錄訂戶的 email 屬性進行比較(在 list.jsp 中):

    <web-app>
    <context-param>
    <param-name>adminEmail</param-name>
    <param-value>admin@localhost</param-value>
    </context-param>
    <context-param>
    <param-name>javax.servlet.jsp.jstl.sql.dataSource</param-name>
    <param-value>jdbc/OracleDS</param-value>
    </context-param>
    ...
    <resource-ref>
    <res-ref-name>jdbc/OracleDS</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
    </resource-ref>
    ...
    </web-app>
    

     

    只有在 web.xml 中指定了電子郵件地址的用戶才可以執行 list.jsp 頁面。因此,您必須在部署應用程序后以 admin@localhost 的身份進行訂閱。當使用 adminEmail 地址登錄時,login.jsp 頁面將該請求轉給 list.jsp。該示例應用程序中使用的此身份驗證機制演示了操作方法(如 loginAction())如何根據可編程條件返回不同的結果(listprofile)。實際的應用程序將使用基于 HTTP 或基于表單的身份驗證驗證管理員的身份。

    list.jsp 頁面使用 JSTL 標記執行 SQL 查詢,并創建一個用于保存結果集的 subscriberList 變量。該變量被傳遞給 <h:dataTable>,后者迭代結果集的各行,并調用每個行的標記主體。每個 <h:column> 標記都包含一個 <h:outputText value="#{row...}"/> 標記(用于呈現當前單元格的值)和一個作為表格的表頭的一部分呈現的 facet:

    <!-- list.jsp -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
    <%@ taglib prefix="sql" uri="http://java.sun.com/jstl/sql" %>
    <%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
    <%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
    <c:if test="${subscriber == null || !subscriber.loggedIn}">
    <c:redirect url="/login.faces"/>
    </c:if>
    <c:if test="${subscriber.email != initParam.adminEmail}">
    <c:redirect url="/profile.faces"/>
    </c:if>
    <sql:query var="subscriberList" scope="request">
    SELECT * FROM subscribers ORDER BY subscriberEmail
    </sql:query>
    <f:view>
    ...
    <h:form id="list">
    <h:dataTable id="table" var="row"
    value="#{subscriberList}"
    border="1" cellpadding="5">
    <h:column>
    <f:facet name="header">
    <h:outputText value="#{labels.email}"/>
    </f:facet>
    <h:outputText value="#{row.subscriberEmail}"/>
    </h:column>
    <h:column>
    <f:facet name="header">
    <h:outputText value="#{labels.password}"/>
    </f:facet>
    <h:outputText value="#{row.subscriberPassword}"/>
    </h:column>
    ...
    </h:dataTable>
    </h:form>
    </body>
    </html>
    </f:view>
    

     

    由于 JSF 不支持 page 作用域,因此 <sql:query> 標記使用 list.jsp 頁面中的 request 作用域。如果您忘了指定 JSF 支持的作用域,則 JSTL 標記將使用默認的頁面作用域,且這兩個標記庫的標記將無法通信。因此,請確保結合使用 JSF 和 JSTL 的標記時使用一個通用的作用域(request、sessionapplication)。

    總結

    本文演示了一個基于 JSF 的 Web 應用程序,它使用 DAO 模式、JDBC、SQL、TopLink 和 JSTL 訪問關系數據庫。您可以在開發實際應用程序時應用本文介紹的技術,并且您可以使用 Oracle 數據庫、Oracle Application Server Containers for J2EE、TopLink 對象關系映射框架和 JSF 引用實現測試本文的示例。


    Andrei Cioroianu (
    devtools@devsphere.com) 是 Devsphere 的創始人,這是一家 Java 框架、XML 咨詢和 Web 開發服務的供應商。Andrei 編寫了許多 Java 文章,分別由 Oracle 技術網、ONJavaJavaWorldJava Developer's Journal 發表。他還與別人合著了 Java XML Programmer's Reference Professional Java XML 兩書(均由 Wrox Press 出版)。

     

     

    posted on 2008-04-27 16:03 Jarod.cn.LuLuLife 閱讀(358) 評論(0)  編輯  收藏


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    導航

    統計

    公告

    我的知識Blog!

    常用鏈接

    留言簿(3)

    隨筆檔案

    文章檔案

    Image

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 四虎影视精品永久免费| 成人免费视频88| 99久久久国产精品免费蜜臀 | 亚洲av无码一区二区三区乱子伦| 国产成人无码区免费网站| 免费国产成人高清在线观看麻豆 | 久久精品国产亚洲沈樵| 日日摸夜夜添夜夜免费视频| 精品亚洲一区二区| 成人毛片免费在线观看| 一级全免费视频播放| 国产成人涩涩涩视频在线观看免费| 一个人看www免费高清字幕| 亚洲国产综合自在线另类| 亚洲A丁香五香天堂网| 国产精品亚洲专区一区| 四虎www成人影院免费观看| 精品亚洲AV无码一区二区三区 | 亚洲真人无码永久在线| 亚洲精品无码高潮喷水A片软| 亚洲一区二区高清| 日韩免费a级毛片无码a∨| 久久亚洲精品国产亚洲老地址| 在免费jizzjizz在线播| 亚洲精品成人网站在线播放| 国产精品免费看香蕉| 91av免费观看| 亚洲色欲色欱wwW在线| 国产亚洲综合色就色| 免费人成网站在线播放| 久久精品无码一区二区三区免费| 中文字幕无码日韩专区免费 | 国产免费牲交视频| 久99久精品免费视频热77| 91亚洲国产在人线播放午夜| 国产1000部成人免费视频| 岛国精品一区免费视频在线观看| 亚洲精品无码久久久久sm| 国产成人青青热久免费精品| 免费国产作爱视频网站| 香港一级毛片免费看|