Professional Documents
Culture Documents
•
开发人员:J2EE
使用 JSF 构建数据库驱动的应用程序
作者:Andrei Cioroianu
本文相关下载:
示例代码
Oracle TopLink 10g
Oracle 数据库 10g
OC4J 10g
Oracle ADF 组件
JavaServer Faces
应用程序概述
图 1 显示了订阅表单。该示例应用程序不分发任何时事通讯,而实际的应用程序将向管理
者、开发人员和管理员发送不同的时事通讯。(同一订户可能收到多个时事通讯。)该示例
应用程序将用户配置文件保存到关系数据库中,并允许订户更改他们的配置文件。
图 1:“订阅”窗体
应用程序配置
<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>
模型视图分离
资源
Java Web 应用程序(包括那些基于 JSF 的应用程序)使用 JSP 和
JavaBean 将表现形式与应用程序逻辑分离。使用类似 JSP 2.0 EL
Oracle JDeveloper 10g
的表达式语言 (EL) 可以将 JSF 标记的属性绑定到 bean 属性(有
(10.1.3) 开发人员预览
时为 bean 方法)。JSF EL 通常使用 #{...}语法(而非
版为您提供了单一
${...} 构造),以便用于 JSP 1.2 和 JSP 2.0。它还允许 JSF 在
IDE,通过它您除了可
任何必要的情况下求解表达式(和重新求解),而不是让 JSP 容
以编写 Java 代码以
器控制表达式的求解。在有意义的情况下,JSF EL 绑定是双向
外,还可以定义
的。例如,UI 组件可以取得 bean 属性的值并将其作为默认值提
toplink 映射以及直观
供给用户。当用户提交表单数据时,此 UI 组件会更新 bean 属
构建 JSF 和 JSP 页
性,以便应用程序逻辑可以处理该新值。 面。Oracle JDeveloper
还提供了由丰富的
JavaBean(模型)、JSF 页面(视图)和 Faces Servlet(控制器) JSF 组件组成的 ADF
具有定义良好的角色,从而分离了对象模型、表现方式和请求处 Faces 集,您可以在应
理。您可以更深入一步,将 UI 特有的 Java 代码与其实例维护和 用程序中重用这些组
处理数据的类分离。一个可行的解决方案是构建对用户界面一无 件。
所知的模型 bean,然后使用 JSF 特有的方法(如操作和验证器,
本文稍后将对其进行介绍)扩展这些模型类。 1) Oracle JDeveloper
10g (10.1.3) 开发人员
该示例应用程序有两个模型 bean(LoginInfo 和 预览版下载
Subscriber),它们由两个视图 bean(LoginInfoBean 和
SubscriberBean)扩展,JSF 对这两个视图 bean 的实例进行 2) 使用 JDeveloper 构
管理。每个视图 bean 实例都有一个标识符和一个作用域(您可以 建 JSF/TopLink
<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>
Bean 与页面的关系
托管 bean 和 JSF
数据访问方法 JSF 页面
操作
SubscriberDAO. LoginInfoBean.
login.jsp
select() loginAction()
SubscriberDAO. SubscriberBean. subscribe.
insert() subscribeAction() jsp
SubscriberDAO. SubscriberBean.
profile.jsp
update() profileAction()
SubscriberBean.
SubscriberDAO. unsubscribe.
unsubscribeAction
delete() jsp
()
表 2:绑定到 JSF 页面中的提交按钮的 JSF 操作使用的 DAO 方法
页面到页面的导航
每个操作方法都返回一个名为“结果”的字符串。JSF 使用导航处理程序确定对每个结果所执行
的操作。如果操作方法返回 null,则必须重新显示同一页面。否则,会根据返回的结果显示
其他页面。表 3 包含该示例 Web 应用程序的 JSF 表单、可能的结果以及针对每个结果显示的
页面。
<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>
package jsfdb.model;
package jsfdb.model;
...
if (isDeveloper())
count++;
if (isAdministrator())
count++;
return count;
}
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;
throws SubscribeException;
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;
}
...
}
package jsfdb.model;
...
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.logging.Level;
import java.util.logging.Logger;
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;
package jsfdb.model.err;
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 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"));
}
});
}
package jsfdb.view;
import jsfdb.model.Subscriber;
...
public class SubscriberBean extends Subscriber {
...
private transient boolean loggedIn = false;
● loginAction() 如何选择行,
● subscribeAction() 如何插入新行,
● profileAction() 如何更新现有行,
● unsubscribeAction() 如何删除行。
下一部分(JSF 视图和数据验证)
SELECT subscriberPassword,
subscriberName,
managerFlag,
developerFlag,
administratorFlag,
subscriptionType
FROM subscribers
WHERE subscriberEmail=?
package jsfdb.model.dao;
...
import oracle.toplink.expressions.ExpressionBuilder;
import oracle.toplink.queryframework.ReadObjectQuery;
import oracle.toplink.sessions.Session;
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;
}
...
}
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;
package jsfdb.view;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import java.util.ResourceBundle;
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());
}
}
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();
}
}
...
}
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;
}
}
...
}
developerFlag=?,
administratorFlag=?,
subscriptionType=?
WHERE subscriberEmail=?
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();
}
}
...
}
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;
}
}
...
}
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();
}
}
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;
}
}
...
}
第 7 步:在 Import Tables from Database 窗口中的 Table Name Pattern 域中,输入
SUBSCRIBERS,单击 Get Table Names,从左侧 Available Tables 窗格中选择 SUBSCRIBERS
表,将它添加到右侧的 Selected Tables 窗格,然后单击 OK。
JSF 视图和数据验证
<f:view>
<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>
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
<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">
<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>
<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>
由 unsubscribe.jsp 呈现的表单包含两个绑定到不同操作方法的提交按钮:
</h:form>
...
</f:view>
unsubscribeAction() 方法从数据库中删除订户的配置文件。先前部分(JavaBean 和数
据访问对象)介绍了所有操作方法的代码。
package jsfdb.view;
...
import javax.faces.component.UIComponent;
import javax.faces.component.EditableValueHolder;
import javax.faces.context.FacesContext;
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
<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;
}
...
}
...
}
<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>
<f:view>
...
<h:form id="list">
<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>
总结
请为本文评定等级:
优秀 良好 一般 低于一般水平 较差
•••••••••
把您的意见发送给我们