blob: 6913135cb3786bcf6d6512bac7486ad49f1b169d [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
= 通过数据库生成 JavaServer Faces 2.x CRUD 应用程序
:jbake-type: tutorial
:jbake-tags: tutorials
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: 通过数据库生成 JavaServer Faces 2.x CRUD 应用程序 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, 通过数据库生成 JavaServer Faces 2.x CRUD 应用程序
在本教程中,您将使用 NetBeans IDE 来创建一个与后端数据库进行交互的 Web 应用程序。使用该应用程序可以查看和修改数据库中包含的数据,也称为 _CRUD_(创建、读取、更新和删除)功能。您将开发的应用程序依赖于以下技术。
* *JavaServer Faces (JSF) 2.x*,用于前端 Web 页、验证处理和请求-响应周期的管理。
* *Java Persistence API (JPA) 2.0*,通过 EclipseLink 从数据库中生成实体类,并管理事务。(EclipseLink JPA 的引用实现,也是 GlassFish Server 的默认持久性提供器。)
* *Enterprise JavaBeans (EJB) 3.1*,提供了用于访问实体类的无状态 EJB,并且包含了应用程序的业务逻辑。
IDE 提供了两种可为应用程序生成所有代码的向导。一种是 <<generateEntity,"Entity Classes from Database"(通过数据库生成实体类)向导>>,您可以通过所提供的数据库来生成实体类。在创建实体类之后,可使用<<jsfPagesEntityClasses,“通过实体类创建 JSF 页”向导>>为实体类创建 JSF 受管 Bean EJB,以及一组用于处理实体类数据视图的 Facelets 页。本教程的最后部分<<explore,了解应用程序>>为可选章节,其中提供了大量练习有助于您更好地理解应用程序并进一步熟悉此 IDE
image::images/netbeans-stamp-80-74-73.png[title="此页上的内容适用于 NetBeans IDE 7.2、7.3、7.4 和 8.0"]
要学完本教程,您需要具备以下软件和资源。
|===
|软件或资源 |要求的版本
|link:https://netbeans.org/downloads/index.html[+NetBeans IDE+] |7.2、7.3、7.4、8.0、Java EE 包
|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Java 开发工具包 (JDK)+] |7 或 8
|link:http://glassfish.dev.java.net/[+GlassFish Server 开源版+] |3.x、4.x
|link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fmysql-consult.zip[+mysql-consult.zip+] (MySQL)
__
link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fjavadb-consult.zip[+javadb-consult.zip+] (JavaDB) |N/A
|===
*注:*
* NetBeans IDE Java EE 包还包括本教程所需的 GlassFish Server(一种 Java EE 兼容的服务器)。
* 要获取本教程的解决方案项目,请下载 link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252FConsultingAgencyJSF20.zip[+ConsultingAgencyJSF20.zip+]。
[[createDB]]
== 创建数据库
本教程使用了一个名为 `consult` 的咨询代理数据库。安装 IDE 时,该数据库并未包括在其中,因此需要首先创建该数据库以按照本教程完成后续的学习。
`consult` 数据库旨在演示 IDE 对处理各种数据库结构所提供的支持范围。因此,该数据库不应作为数据库设计的推荐范例或最佳做法。相反,它尝试将可能会在数据库设计中遇到的许多相关功能包含了进来。例如,`consult` 数据库包含了所有可能的关系类型、复合主键和许多不同的数据类型。有关此数据库结构的更详细概览,请参见下表。
*注:*
* 本教程使用 MySQL 数据库服务器,但也可使用 JavaDB 数据库服务器来完成本教程的学习。要在 JavaDB 中创建该数据库,请下载并解压缩 link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fjavadb-consult.zip[+javadb-consult.zip+] 档案文件。此档案文件包含用于创建、放置和填充 `consult` 数据库的 SQL 脚本。
* 有关配置 IDE 使用 MySQL 的详细信息,请参见link:../ide/mysql.html[+连接 MySQL 数据库+]教程。
* 有关配置 IDE 以使用 JavaDB 的详细信息,请参见link:../ide/java-db.html[+使用 Java DB (Derby) 数据库+]教程。
*MySQL GlassFish 组合:*
如果在使用 MySQL 的同时使用 GlassFish v3 Open Source Edition 3.0.1,则必须确保数据库受口令保护。(有关详细信息,请参见 GlassFish link:https://java.net/jira/browse/GLASSFISH-12221[+问题 12221+]。)如果您使用的是默认 MySQL `root` 帐户和空口令,则可以通过命令行提示符设置口令。
例如,要将口令设置为 `_nbuser_`,请在命令行提示符下输入以下命令。
[source,java]
----
shell> mysql -u root
mysql> UPDATE mysql.user SET Password = PASSWORD('_nbuser_') WHERE User = 'root';
mysql> FLUSH PRIVILEGES;
----
如果收到 '`mysql: command not found`' 错误,则说明尚未将 `mysql` 命令添加到 `PATH` 环境变量中。可以改为通过输入 MySQL 安装的 `bin` 目录的完整路径来调用该命令。例如,如果 `mysql` 命令位于计算机上的 `/usr/local/mysql/bin` 中,请输入以下内容:
[source,java]
----
shell> /usr/local/mysql/bin/mysql -u root
----
有关详细信息,请参见正式的 MySQL 参考手册:
* link:http://dev.mysql.com/doc/refman/5.1/en/default-privileges.html[+确保初始 MySQL 帐户安全+]》
* link:http://dev.mysql.com/doc/refman/5.1/en/invoking-programs.html[+4.2.1. 调用 MySQL 程序+]》
* link:http://dev.mysql.com/doc/refman/5.1/en/setting-environment-variables.html[+4.2.4. 设置环境变量+]》
执行以下步骤以创建数据库并通过 IDE 与其进行连接。
1. 下载 link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fmysql-consult.zip[+mysql-consult.zip+] 并将此档案解压缩到本地系统。在解压缩档案时您将看到用于创建和填充数据库的 SQL 脚本。档案中还包含了用于拖放表的脚本。
2. "Services"(服务)窗口中,展开 "Databases"(数据库)节点,右键单击 "MySQL" 节点并选择 "Start Server"(启动服务器)。
3. 右键单击 MySQL 服务器节点并选择 "Create Database"(创建数据库)。
4. "Create MySQL Database"(创建 MySQL 数据库)对话框中键入 *consult* 作为数据库名称。单击 "OK"(确定)。"Databases"(数据库)节点 (`jdbc:mysql://localhost:3306/consult [root on Default schema]`) 下随即出现一个新的节点。
5. 右键单击新节点并选择 "Connect"(连接)。
6. 从主菜单中选择 "File"(文件)> "Open File"(打开文件)并导航至解压缩的文件 `mysql_create_consult.sql`。单击 "Open"(打开)。该文件会自动在 SQL 编辑器中打开。
image::images/run-sql-script.png[title="在 IDE 的编辑器中打开 SQL 文件"]
. 确保在 SQL 编辑器工具栏的 "Connection"(连接)下拉列表中选择了 `consult` 数据库,然后单击 "Run SQL"(运行 SQL)(image:images/run-sql-btn.png[]) 按钮。
单击 "Run SQL"(运行 SQL)时,"Output"(输出)窗口中出现以下输出内容。
image::images/run-sql-output.png[title=""Output"(输出)窗口提供有关 SQL 执行的信息"]
[[examineDB]]
== 检查数据库结构
要确认已正确创建了表,请展开数据库连接节点下的 "Tables"(表)节点。您可以展开表节点来查看表的列、索引和任意外键。可以右键单击列并选择 "Properties"(属性)来查看有关该列的其他信息。
image::images/services-window-tables.png[title=""Services"(服务)窗口将显示数据库连接、表、表列、索引和外键"]
*注:*如果在 "Tables"(表)节点下未看到任何表,右键单击 "Tables"(表)节点并选择 "Refresh"(刷新)。
`consult` 数据库的结构可看出该数据库包含了具有多种关系和各种字段类型的表。通过数据库创建实体类时,IDE 会为各种字段类型自动生成相应的代码。
image::images/diagram_consult.png[title="consult 数据库的实体关系图"]
下表描述了 `consult` 数据库中的表。
|===
|数据库表 |描述 |设计功能
|CLIENT |咨询机构的客户 |非生成的复合主键(其字段不构成外键)
|CONSULTANT |咨询机构的员工,客户可基于合同对其进行聘用 |包括一个 LONG VARCHAR 类型的简历字段
|CONSULTANT_STATUS |顾问在咨询机构的状态(例如,可能为活动和非活动的状态) |CHAR 类型的非生成主键
|RECRUITER |负责联络客户和顾问的咨询机构的员工
|PROJECT |客户配有咨询机构顾问的项目 |包含了构成 CLIENT 表的外键的两个字段的非生成复合主键
|BILLABLE |顾问在某个项目中工作的小时数,咨询机构据其向相关客户收费 |包括一个 CLOB 类型的工件字段
|ADDRESS |客户账单地址
|PROJECT_CONSULTANT |一个连接表,表示当前为哪些项目指派了哪些顾问 |交叉引用了 PROJECT CONSULTANT,其中前者有一个复合主键
|===
`consult` 数据库包含了多种关系。在通过数据库创建实体类时,IDE 将基于列的 SQL 类型自动生成相应的 Java 类型的属性。下表描述了 `consult` 数据库的实体关系。(未显示反向关系。)
|===
|实体 |相关实体 |关系信息 |描述
|CLIENT |RECRUITER |进行手动编辑,允许为空值的一对一关系;如果不进行手动编辑,允许为空值的一对多关系 |一个 CLIENT 有多个 RECRUITER,而一个 RECRUITER 没有或有一个 CLIENT(如果不进行手动编辑)
|CLIENT |ADDRESS |不允许为空值的一对一关系 |一个 CLIENT 有一个 ADDRESS,而一个 ADDRESS 没有或有一个 CLIENT
|CLIENT |PROJECT |不允许为空值的一对多关系;在 "Project" 实体中,客户字段的值是 "Project" 主键的一部分 |一个 CLIENT 有多个 PROJECT,而一个 PROJECT 只有一个 CLIENT
|CONSULTANT |PROJECT |多对多关系 |一个 CONSULTANT 有多个 PROJECT,而一个 PROJECT 也有多个 CONSULTANT
|CONSULTANT |BILLABLE |不允许为空值的一对多关系 |一个 CONSULTANT 有多个 BILLABLE,而一个 BILLABLE 有一个 CONSULTANT
|CONSULTANT_STATUS |CONSULTANT |不允许为空值的一对多关系 |一个 CONSULTANT_STATUS 有多个 CONSULTANT,而一个 CONSULTANT 有一个 CONSULTANT_STATUS
|CONSULTANT |RECRUITER |允许为空值的一对多关系 |一个 CONSULTANT 没有或有一个 RECRUITER,而一个 RECRUITER 有多个 CONSULTANT
|BILLABLE |PROJECT |不允许为空值的一对多关系 |一个 BILLABLE 有一个 PROJECT,而一个 PROJECT 有多个 BILLABLE
|===
既然已经创建了数据库,那么就可以创建 Web 应用程序,并使用 "Entity Classes from Database"(通过数据库生成实体类)向导基于数据库表生成实体类。
[[createProject]]
== 创建 Web 应用程序项目
在本练习中,您将创建一个 Web 项目并将 JavaServer Faces 框架添加到该项目中。在创建项目时,需要在新建项目向导的 "Frameworks"(框架)面板中选择 "JavaServer Faces"
1. 从主菜单中,选择 "File"(文件)> "New Project"(新建项目)(Ctrl-Shift-N 组合键;在 Mac 上为 ⌘-Shift-N 组合键)。
2. "Java Web" 类别中选择 "Web Application"Web 应用程序)。单击 "Next"(下一步)。
3. 键入 `ConsultingAgency` 作为项目名称,并设置项目位置。单击 "Next"(下一步)。
4. 将服务器设置为 "GlassFish",并将 Java EE 版本设置为 "Java EE 6 Web" "Java EE 7 Web"。单击 "Next"(下一步)。
5. "Frameworks"(框架)面板中,选择 "JavaServer Faces" 选项。单击 "Finish"(完成)。
单击 "Finish"(完成),此时 IDE 生成 Web 应用程序项目并在编辑器中打开 `index.xhtml`
[[generateEntity]]
== 通过数据库生成实体类
IDE 中连接到数据库后,可以使用 "Entity Classes from Database"(通过数据库生成实体类)向导基于数据库中的表快速生成实体类。IDE 可以为所选的每个表生成实体类,也可以为相关表生成任何需要的实体类。
1. "Projects"(项目)窗口中,右键单击 `ConsultingAgency` 项目节点,并选择 "New"(新建)> "Entity Classes from Database"(通过数据库生成实体类)。(如果未列出此选项,请选择 "Other"(其他)。然后,在文件向导中,选择 "Persistence"(持久性)类别,再选择 "Entity Classes from Database"(通过数据库生成实体类)。)
2. "Data Source"(数据源)下拉列表中选择 "New Data Source"(新建数据源)以打开 "Create Data Source"(创建数据源)对话框。
3. 键入 `jdbc/consult` 作为 JNDI 名称,并选择 `jdbc:mysql://localhost:3306/consult` 连接作为数据库连接。
image::images/create-datasource.png[title="指定 JNDI 名称和数据库连接以创建数据源"]
. 单击 "OK"(确定)以关闭对话框并返回到向导。`consult` 数据库中的表出现在 "Available Tables"(可用表)列表框中。
. 单击 "Add All"(全部添加)按钮以选择数据库中包含的所有表。单击 "Next"(下一步)。
image::images/new-entities-wizard.png[]
. 键入 `jpa.entities` 作为包名称。
. 确认已选中用于生成命名查询和创建持久性单元的复选框。单击 "Finish"(完成)。
单击 "Finish"(完成),此时 IDE 将在项目的 `jpa.entities` 包中生成实体类。
使用向导通过数据库创建实体类时,IDE 会检查数据库表之间的关系。在 "Projects"(项目)窗口中,如果展开 `jpa.entities` 包节点,则可以看到 IDE 为每个表(`PROJECT_CONSULTANT` 表除外)生成了一个实体类。因为 `PROJECT_CONSULTANT` 是一个连接表,所以 IDE 没有为其创建实体类。
image::images/projects-window-entities.png[title="显示所生成实体类的 "Projects"(项目)窗口的屏幕快照"]
IDE 还为带有复合主键的表生成了两个额外的类:`CLIENT` `PROJECT`。这些表的主键类(`ClientPK.java` `ProjectPK.java`)在其名称后附加了 `PK`
如果查看为实体类生成的代码,可以发现向导为自动生成的 ID 字段添加了 `@GeneratedValue` 标注,还为实体类中的某些字段添加了 `@Basic(optional = "false")` 标注。基于 `@Basic(optional = "false")` 标注,“通过实体类创建 JSF 页”向导可以生成包含检查的代码,这些检查可防止那些字段出现不允许为空值的列违规。
[[jsfPagesEntityClasses]]
== 通过实体类生成 JSF
创建了实体类之后,便可以创建用于显示和修改数据的 Web 接口。您将使用“通过实体类创建 JSF 页”向导来生成 JavaServer Faces 页。向导生成的代码将基于实体类中包含的持久性标注。
对于每个实体类,向导都会生成以下文件。
* 扩展 ``AbstractFacade.java`` 的无状态会话 Bean
* JSF 会话范围的受管 Bean
* 包含 CRUD 功能的四个 Facelets 文件(`Create.xhtml``Edit.xhtml``List.xhtml` `View.xhtml`)的目录
向导还会生成以下文件。
* 包含实体实例的创建、检索、修改和删除业务逻辑的 ``AbstractFacade.java``
* JSF 受管 Bean 所使用的实用程序类(`JsfUtil` `PaginationHelper`
* 本地化消息的属性包,以及项目的 Faces 配置文件(如果不存在 `faces-config.xml` 文件,则会创建一个)中的相应条目
* 辅助 Web 文件(包括所呈现组件的默认样式表)和一个 Facelets 模板文件
生成 JSF 页:
1. "Projects"(项目)窗口中,右键单击项目节点,然后选择 "New"(新建)> "JSF Pages from Entity Classes"(通过实体类创建 JSF 页)打开该向导。(如果未列出此选项,请选择 "Other"(其他)。然后,在文件向导中,选择 "JavaServer Faces" 类别,然后选择 "JSF Pages from Entity Classes"(通过实体类创建 JSF 页)。)
"Available Entity Classes"(可用的实体类)框列出了项目中所包含的七个实体类。该框并未列出可嵌入类(`ClientPK.java` `ProjectPK.java`)。
. 单击 "Add All"(全部添加)以将所有类移动到 "Selected Entity Classes"(选定的实体类)框中。
image::images/newjsf-wizard.png[title=""New JSF Pages from Entity Classes"(通过实体类新建 JSF 页)向导显示项目中包含的所有实体类"]
单击 "Next"(下一步)。
. 在向导的“步骤 3”即 "Generate JSF Pages and Classes"(生成 JSF 页和类)中,键入 `jpa.session` 作为 JPA 会话 Bean 包。
. 键入 `jsf` 作为 JSF 类包。
. "Localization Bundle Name"(本地化包名称)字段中输入 "`/resources/Bundle`"。这样将生成一个名为 `resources` 的包,其中将包含 `Bundle.properties` 文件。(如果将此字段保留为空,则在项目的默认包中将创建属性包。)
image::images/newjsf-wizard2.png[title="为生成的文件指定包名称和文件夹名称"]
要使 IDE 能更好地适应您的项目约定,可以定制由向导生成的任何文件。单击 "Customize Template"(定制模板)链接以修改向导所使用的文件模板。
image::images/customize-template.png[title="为向导所生成的文件定制模板"]
通常,可以使用 "Template Manager"(模板管理器)("Tools"(工具)> "Templates"(模板))来访问和更改 IDE 维护的所有模板。
. 单击 "Finish"(完成)。IDE `jpa.session` 包中生成无状态会话 Bean,并在 `jsf` 包中生成 JSF 会话范围的受管 Bean。每个无状态会话 Bean 用于处理相应实体类的操作,其中包括通过 Java 持久性 API 创建、编辑和销毁实体类的实例。每个 JSF 受管 Bean 用于实现 `javax.faces.convert.Converter` 接口,并执行将相应实体类的实例转换为 `String` 对象的操作,反之亦然。
如果展开 "Web Pages"Web 页)节点,则可以看到 IDE 为每个实体类生成了一个文件夹。每个文件夹都包含 `Create.xhtml``Edit.xhtml``List.xhtml` `View.xhtml` 文件。IDE 还通过向每个 `List.xhtml` 页中插入链接来修改 `index.xhtml` 文件。
image::images/projects-jsfpages.png[title="向导为每个实体类生成 Facelets 页"]
每个 JSF 受管 Bean 都对应四个相应的 Facelets 文件,并且包含调用相应会话 Bean 中的方法的代码。
展开 `resources` 文件夹节点以查找由向导生成的 `jsfcrud.css` 默认样式表。如果在编辑器中打开应用程序欢迎页面 (`index.xhtml`) Facelets 模板文件 (`template.xhtml`),将会看到该文件包含了对样式表的引用。
[source,java]
----
<h:outputStylesheet name="css/jsfcrud.css"/>
----
每个实体类所对应的四个 Facelets 文件中的每个文件都使用 Facelets 模板文件。
如果展开 "Source Packages"(源包)节点,则可以看到向导生成的会话 BeanJSF 受管 Bean、实用程序类和属性包。
image::images/projects-generated-classes70.png[title=""Projects"(项目)窗口中显示向导所生成类的 "Source Packages"(源包)目录的屏幕快照"]
为了注册属性包的位置,向导还生成了一个 Faces 配置文件 (`faces-config.xml`)。如果展开 "Configuration File"(配置文件)节点并在 XML 编辑器中打开 `faces-config.xml`,则可以看到该文件包含了以下条目。
[source,xml]
----
<application>
<resource-bundle>
<base-name>/resources/Bundle</base-name>
<var>bundle</var>
</resource-bundle>
</application>
----
此外,如果展开新的 `resources` 包,则会发现 `Bundle.properties` 文件,该文件包含了客户端默认语言的消息。这些消息是由实体类属性派生而来的。
要添加新的属性包,请右键单击 `Bundle.properties` 文件并选择 "Customize"(定制)。使用 "Customizer"(定制器)对话框可以将新的语言环境添加到您的应用程序中。
[[explore]]
== 了解应用程序
既然您的项目已包含了实体类、用于控制实体类的 EJB 会话 Bean JSF 驱动的用于显示和修改数据库的前端,那么请尝试运行该项目以查看结果。
以下是一系列可选的小练习,可用来进一步熟悉应用程序,以及 IDE 所提供的功能。
* <<completedProject,检查已完成的项目>>
* <<populateDB,使用 SQL 脚本填充数据库>>
* <<editorSupport,了解 Facelets 页中的编辑器支持>>
* <<dbIntegrity,通过字段验证来了解数据库完整性>>
* <<editEntity,编辑实体类>>
[[completedProject]]
=== 检查已完成的项目
1. 要运行项目,请在 "Projects"(项目)窗口中右键单击项目节点并选择 "Run"(运行),或单击主工具栏中的 "Run Project"(运行项目)(image:images/run-project-btn.png[]) 按钮。
当显示应用程序的欢迎页面时,将提供一个链接列表,用于查看包含在每个数据库表中的条目。
image::images/welcome-page-links.png[title="用于显示每个表的数据库内容的链接"]
当完成了 "JSF Pages from Entity Classes"(通过实体类创建 JSF 页)向导时,链接将添加到欢迎页面 (`index.xhtml`)。这些链接将作为 Facelets 页(为“咨询机构”数据库提供 CRUD 功能)的入口点。
[source,xml]
----
<h:body>
Hello from Facelets
<h:form>
<h:commandLink action="/address/List" value="Show All Address Items"/>
</h:form>
<h:form>
<h:commandLink action="/billable/List" value="Show All Billable Items"/>
</h:form>
<h:form>
<h:commandLink action="/client/List" value="Show All Client Items"/>
</h:form>
<h:form>
<h:commandLink action="/consultant/List" value="Show All Consultant Items"/>
</h:form>
<h:form>
<h:commandLink action="/consultantStatus/List" value="Show All ConsultantStatus Items"/>
</h:form>
<h:form>
<h:commandLink action="/project/List" value="Show All Project Items"/>
</h:form>
<h:form>
<h:commandLink action="/recruiter/List" value="Show All Recruiter Items"/>
</h:form>
</h:body>
----
. 单击 "`Show All Consultant Items`" 链接。查看上面的代码,则可以看到目标页为 `/consultant/List.xhtml`。(在 JSF 2.x 中,由于隐式导航所以文件扩展名是推断出来的。)
image::images/empty-consultants-list.png[title="Consultants 表当前为空"]
数据库目前不包含任何样例数据。可以通过单击 "`Create New Consultant`" 链接和使用提供的 Web 窗体手动添加数据。此操作将触发 `/consultant/Create.xhtml` 页进行显示。还在 IDE 中运行 SQL 脚本以使用样例数据填充表。以下子部分对这两个选项进行了说明。
您可以单击索引链接返回到欢迎页面列出的链接。这些链接提供了保存在每个数据库表中数据的视图,并触发了每个实体文件夹中 `List.xhtml` 文件的显示。稍后会有说明,在将数据添加到表之后,会显示其他链接,其中每个条目都允许您查看 (`View.xhtml`)、编辑 (`Edit.xhmtl`) 和销毁单个表记录的数据。
*注:*如果应用程序部署失败,请参见下面的<<troubleshooting,疑难解答>>部分。(另请参见link:mysql-webapp.html#troubleshoot[+使用 MySQL 数据库创建简单的 Web 应用程序+]的“疑难解答”部分。)
[[populateDB]]
=== 使用 SQL 脚本填充数据库
运行提供的脚本,该脚本会生成数据库表的样例数据。脚本 (`mysql_insert_data_consult.sql`) 包含在咨询机构数据库 zip 文件中,该文件可从<<requiredSoftware,所需软件表>>中下载。
根据所使用的数据库服务器(MySQL JavaDB),您可以运行提供的脚本,该脚本会为数据库表生成样例数据。对于 MySQL,该脚本为 `mysql_insert_data_consult.sql`。对于 JavaDB,该脚本为 `javadb_insert_data_consult.sql`。两个脚本分别包含在各自的档案文件中,可以通过<<requiredSoftware,所需软件表>>进行下载。
1. 从主菜单中选择 "File"(文件)> "Open File"(打开文件),然后导航到脚本在您的计算机上的位置。单击 "Open"(打开)。文件会自动在 IDE SQL 编辑器中打开。
2. 确保在 SQL 编辑器工具栏的 "Connection"(连接)下拉列表中选择了 `consult` 数据库。
image::images/run-sql-insert.png[title="在 IDE 的 SQL 编辑器中打开脚本"]
在编辑器中右键单击并选择 "Run Statement"(运行语句),或单击 "Run SQL"(运行 SQL)(image:images/run-sql-btn.png[]) 按钮。您可以在 "Output"(输出)窗口中查看脚本运行的结果。
. 重新启动 GlassFish Server。这是使服务器可以重新加载和缓存 `consult` 数据库中包含的新数据所必需的步骤。为此,请在 "Output" 窗口中单击 "GlassFish Server" 标签("GlassFish Server" 标签显示服务器日志。),然后单击左旁注中的 "Restart Server"(重新启动服务器)(image:images/glassfish-restart.png[]) 按钮。服务器即会停止,然后重新启动。
. 重新运行项目,然后单击 "`Show All Consultant Items`" 链接。将看到该列表不再为空。
[.feature]
--
image::images/consultants-list-small.png[role="left", link="images/consultants-list.png"]
--
=== NetBeans 数据库支持
可以使用 IDE 的数据库表查看器来显示和修改直接在数据库中维护的表数据。例如,右键单击 "Services"(服务)窗口中的 `consultant` 表,然后选择 "View Data"(查看数据)。
image::images/view-data.png[title="从数据库表的右键单击菜单中选择 "View Data"(查看数据)"]
在编辑器的顶部将显示用于执行操作的 SQL 查询,下面则显示表的图形视图。
[.feature]
--
image::images/view-data-table-small.png[role="left", link="images/view-data-table.png"]
--
在窗体元格内双击以对数据进行内联修改。单击 "Commit Records"(提交记录)(image:images/commit-records-icon.png[]) 图标以提交对数据库的更改。
图形视图提供了更多的功能。有关详细信息,请参见 link:../../docs/ide/database-improvements-screencast.html[+NetBeans IDE 中的数据库支持+]。
[[editorSupport]]
=== 了解 Facelets 页中的编辑器支持
1. 在编辑器中打开 `/consultant/List.xhtml` 页。第 8 行表明该页的呈现依赖于 Facelets `template.xhtml` 文件。
[source,java]
----
<ui:composition template="/template.xhtml">
----
要显示行号,请右键单击编辑器的左旁注,然后选择 " Show Line"(显示行号)。
. 使用 IDE "Go to File"(转至文件)对话框来打开 `template.xhtml`。按 Alt-Shift-O 组合键(在 Mac 上为 Ctrl-Shift-O 组合键),然后开始键入 `template`
image::images/go-to-file.png[title="使用 "Go to File"(转至文件)对话框快速打开项目文件"]
单击 "OK"(确定)(或按 Enter 键)。
. 模板将使用 `<ui:insert>` 标记将其他文件的内容插入到其标题和正文中。将光标置于 `<ui:insert>` 标记上,然后按 Ctrl-空格键调用文档弹出式窗口。
image::images/doc-popup.png[title="在 Facelets 标记上按 Ctrl-空格键调用文档弹出式窗口"]
可以在 JSF 标记及其属性上按 Ctrl-空格键调用文档弹出式窗口。您查看的文档是从正式的 link:http://javaserverfaces.java.net/nonav/docs/2.1/vdldocs/facelets/index.html[+JSF 标记库文档+]所提供的描述中获取的。
. 切换回 `List.xhtml` 文件(按 Ctrl-Tab 组合键)。`<ui:define>` 标记用来定义将应用于模板标题和正文的内容。为每个实体类生成的所有 4 Facelets 文件(`Create.xhtml``Edit.xhtml``List.xhtml` `View.xhtml`)都使用了此模式。
. 将您的光标置于任何 EL 表达式(用于包含在 `Bundle.properties` 文件中的本地化消息)上。按 Ctrl-空格键查看本地化消息。
[.feature]
--
image::images/localized-messages-small.png[role="left", link="images/localized-messages.png"]
--
在上图上,可以看到 EL 表达式解析为 "`List`",该表达式将应用到模板标题,并且可以从呈现在浏览器中的页面得到验证。
. 滚动到文件底部并找到 `Create New Consultant` 链接的代码(第 92 行)。如下所示:
[source,java]
----
<h:commandLink action="#{consultantController.prepareCreate}" value="#{bundle.ListConsultantCreateLink}"/>
----
. `commandLink` `action` 属性上按 Ctrl-空格键以调用文档弹出式窗口。
`action` 属性表示在浏览器中单击链接时处理请求的方法。将提供以下文档:
_方法表达式,用于表示在用户激活此组件时要调用的应用程序操作。该表达式的值必须为一个没有参数且返回对象(调用其 toString() 以派生逻辑结果)的公共方法,该对象将被传递到此应用程序的 NavigationHandler_
换句话说,`action` 值通常是指 JSF 受管 Bean 中的一个方法,其值为 `String`。然后 JSF `NavigationHandler` 会使用该字符串将请求转发到相应的视图中。可通过以下步骤来验证这一点。
. 将光标置于 `consultantController` 上,然后按 Ctrl-空格组合键。编辑器的代码完成表明 `consultantController` 是一个 JSF 受管 Bean
image::images/code-completion-managed-bean.png[title="为 JSF 受管 Bean 提供代码完成"]
. 将光标移动到 `prepareCreate`,然后按 Ctrl-空格组合键。代码完成列出了包含在 `ConsultantController` 受管 Bean 中的方法。
image::images/code-completion-properties.png[title="为类方法提供代码完成"]
. Ctrl 键(在 Mac 上为 键),然后将鼠标悬停在 `prepareCreate` 上。形成了一个链接,您可以直接导航至 `ConsultantController` 受管 Bean 中的 `prepareCreate()` 方法。
image::images/editor-navigation.png[title="使用编辑器导航快速导航源代码"]
. 单击链接并查看 `prepareCreate()` 方法(如下所示)。
[source,java]
----
public String prepareCreate() {
current = new Consultant();
selectedItemIndex = -1;
return "Create";
}
----
该方法将返回 `Create``NavigationHandler` 在后台收集信息,并将 `Create` 字符串应用于以视图(为响应请求而发送)为目标的路径:`/consultant/*Create*.xhtml`。(在 JSF 2.x 中,由于隐式导航所以文件扩展名是推断出来的。)
[[dbIntegrity]]
=== 通过字段验证来了解数据库完整性
1. 在浏览器的 <<consultantsList,Consultants List 页>>中,单击 "`Create New Consultant`" 链接。正如之前子部分中的说明,此操作将触发呈现 `/consultant/Create.xhtml` 页。
2. 在窗体中输入以下详细信息。目前,将 `RecruiterId` `StatusId` 字段保留为空。
|===
|字段 |值
|ConsultantId |2
|Email |jack.smart@jsfcrudconsultants.com
|Password |jack.smart
|HourlyRate |75
|BillableHourlyRate |110
|HireDate |07/22/2008
|Resume |I'm a great consultant.Hire me - You won't be disappointed!
|RecruiterId |---
|StatusId |---
|===
. 单击 "Save"(保存)。当您执行此操作后,将对 `StatusId` 字段标记一个验证错误。
image::images/create-new-consultant.png[title="在窗体中输入样例数据"]
为什么会出现这种情况?重新检查<<er-diagram,“咨询机构”数据库的实体关系图>>。如上面的<<relationships,关系表>>中所述,`CONSULTANT` `CONSULTANT_STATUS` 表共享一个不允许为空值的一对多的关系。所以,`CONSULTANT` 表中的每一个条目都必须包含对 `CONSULTANT_STATUS` 表中条目的引用。这将通过链接这两个表的 `consultant_fk_consultant_status` 外键来表示。
可以通过在 "Services"(服务)窗口中展开一个表的 "Foreign Keys"(外键)节点(Ctrl-5 组合键;在 Mac 上为 ⌘-5 组合键)来查看表中保存的外键。
image::images/consultant-fk.png[title="在 "Services"(服务)窗口中检查外键属性"]
. 要解决验证错误,请从 `StatusId` 下拉列表中选择 `entity.ConsultantStatus[statusId=A]`
*注:*`RecruiterId` 字段可保留为空。正如<<er-diagram,数据库实体关系图>>中所示,在 `CONSULTANT` `RECRUITER` 表之间存在一个允许为空值的一对多的关系,这就意味着 `CONSULTANT` 中的条目不需要与一个 `RECRUITER` 条目相关联。
. 单击 "Save"(保存)。将显示一条消息,表明顾问条目已成功保存。如果单击 `Show All Consultant Items`,将看到新条目列在表中。
通常,生成的 Facelets 页会为产生下列问题的用户输入提供出错信息:
* 不允许为空值的表单元格中出现了空字段。
* 对不可更改的数据(例如主键)进行了修改。
* 插入数据的类型不正确。
* 当用户视图与数据库不再同步时对数据进行了修改。
[[editEntity]]
=== 编辑实体类
在前面的子部分中,您看到了 `StatusId` 下拉列表是如何为您提供不那么容易使用的 `entity.ConsultantStatus[statusId=A]` 选项。您可能已经注意到:此下拉列表中针对每一项显示的文本都是每个遇到的 `ConsultantStatus` 实体(即,调用了实体类的 `toString()` 方法)的字符串表示。
该子部分将演示您可以使用编辑器的代码完成、文档和导航支持作此结论的方式。此外,还教您为下拉列表准备更加易于使用的消息。
1. 在编辑器中打开 `/consultant/Create.xhtml` 文件。这是您刚在浏览器中看到的 "Create New Consultant" 窗体。向下滚动到 `StatusId` 下拉列表的代码处(如下面*粗体*所示)。
[source,xml]
----
<h:outputLabel value="#{bundle.CreateConsultantLabel_resume}" for="resume" />
<h:inputTextarea rows="4" cols="30" id="resume" value="#{consultantController.selected.resume}" title="#{bundle.CreateConsultantTitle_resume}" />
*<h:outputLabel value="#{bundle.CreateConsultantLabel_statusId}" for="statusId" />
<h:selectOneMenu id="statusId" value="#{consultantController.selected.statusId}" title="#{bundle.CreateConsultantTitle_statusId}" required="true" requiredMessage="#{bundle.CreateConsultantRequiredMessage_statusId}">
<f:selectItems value="#{consultantStatusController.itemsAvailableSelectOne}"/>
</h:selectOneMenu>*
<h:outputLabel value="#{bundle.CreateConsultantLabel_recruiterId}" for="recruiterId" />
<h:selectOneMenu id="recruiterId" value="#{consultantController.selected.recruiterId}" title="#{bundle.CreateConsultantTitle_recruiterId}" >
<f:selectItems value="#{recruiterController.itemsAvailableSelectOne}"/>
</h:selectOneMenu>
</h:panelGrid>
----
. 检查应用于 `<f:selectItems>` 标记的 `value``value` 属性决定了为下拉列表中每项显示的文本。
`itemsAvailableSelectOne` 上按 Ctrl-空格组合键。编辑器的代码完成表示 `ConsultantStatusController` `getItemsAvailableSelectOne()` 方法返回了 `SelectItem` 对象的数组。
image::images/code-completion-returned-object.png[title="代码完成显示方法的返回类"]
. Ctrl 键(在 Mac 上为 键),然后将鼠标悬停在 `itemsAvailableSelectOne` 上。形成了一个链接,您可以直接导航至 `ConsultantStatus` 实体源代码中的 `getItemsAvailableSelectOne()` 方法。单击该链接。
. 将光标置于方法签名中的 `SelectItem[]` 返回值上,然后按 Ctrl-空格键调用文档弹出式窗口。
image::images/documentation-select-item.png[title="按 Ctrl-空格键调用文档支持"]
单击 "documentation"(文档)窗口中的 "web browser"Web 浏览器)(image:images/web-browser-icon.png[]) 图标以在外部 Web 浏览器中打开 Javadoc
正如您所看到的,`SelectItem` 类属于 JSF 框架。文档中提到的 `UISelectOne` 组件使用您在上述<<markup,步骤 1>> 中检查的标记中的 `<h:selectOneMenu>` 标记来表示。
. Ctrl 键(在 Mac 上为 键),然后将鼠标悬停在 `findAll()` 上。随即出现一个弹出式窗口,显示方法签名。
image::images/method-signature.png[title="在编辑器中查看方法签名的弹出式窗口"]
可以看到此处的 `ejbFacade.findAll()` 返回了 `ConsultantStatus` 对象的 `List`
. 导航至 `JsfUtil.getSelectItems`。将鼠标悬停在 `getSelectItems` 上并按 Ctrl 键(在 Mac 上为 ⌘),然后单击显示的链接。
*注:*回想一下,`JsfUtil` 是在您完成<<jsfPagesEntityClasses,“通过实体类创建 JSF 页”向导>>时生成的一个实用程序类。
该方法对实体列表(即 `ConsultantStatus` 对象的 `List`)执行循环操作,为每个实体创建 `SelectItem`。如下面*粗体*所示,每个 `SelectItem` 都是使用实体对象和对象的_标签_来创建的。
[source,java]
----
public static SelectItem[] getSelectItems(List<?> entities, boolean selectOne) {
int size = selectOne ? entities.size() + 1 : entities.size();
SelectItem[] items = new SelectItem[size];
int i = 0;
if (selectOne) {
items[0] = new SelectItem("", "---");
i++;
}
*for (Object x : entities) {
items[i++] = new SelectItem(x, x.toString());
}*
return items;
}
----
该标签使用实体的 `toString()` 方法来创建,并且在响应中呈现对象时是对象的表示。(请参见 `SelectItem(java.lang.Object value, java.lang.String label)` 构造函数的 Javadoc 定义。)
既然您已在查看下拉列表中的项时,验证了实体 `toString()` 方法正是浏览器中所呈现的内容,那么请修改 `ConsultantStatus` `toString()` 方法。
. 在编辑器中打开 `ConsultantStatus` 实体类。修改 `toString` 方法以返回 `statusId` `description`。这些是对应于 `CONSULTANT_STATUS` 表的两列的实体属性。
[source,java]
----
public String toString() {
return *statusId + ", " + description;*
}
----
. 重新运行项目。当浏览器显示欢迎页面时,单击 `Show All Consultant Items` 链接,然后单击 `Create New Consultant`
检查 `StatusId` 下拉列表。将看到现在显示的是数据库的 `CONSULTANT_STATUS` 表中所包含的一个记录的状态 ID 和描述。
image::images/drop-down.png[title=""StatusId" 下拉列表根据 ConsultantStatus 实体的 toString() 方法显示各项"]
[[troubleshooting]]
== 疑难解答
根据您的配置不同,将应用程序部署到服务器可能会失败,您可能会在 "Output"(输出)窗口中看到以下消息。
[source,java]
----
GlassFish Server 4 is running.
In-place deployment at /MyDocuments/ConsultingAgency/build/web
GlassFish Server 4, deploy, null, false
/MyDocuments/ConsultingAgency/nbproject/build-impl.xml:1045: The module has not been deployed.
See the server log for details.
----
最常见的失败原因是,在服务器上生成 JDBC 资源时出现问题。如果是这样,您可能会在 "Output"(输出)窗口的 "server log"(服务器日志)标签中看到类似于以下内容的消息。
[source,java]
----
Severe: Exception while preparing the app : Invalid resource : jdbc/consult__pm
com.sun.appserv.connectors.internal.api.ConnectorRuntimeException: Invalid resource : jdbc/consult__pm
----
如果 "server log"(服务器日志)标签未打开,您可以通过在 "Services"(服务)窗口中右键单击 "GlassFish Server" 节点并选择 "View Domain Server Log"(查看域服务器日志)来打开此标签。
此应用程序需要两个 JDBC 资源:
* JDBC 资源或数据源。此应用程序使用 JNDI 查找来定位 JDBC 资源。如果在持久性单元 (`persistence.xml`) 中查找,您可以看到此应用程序的 JTA 数据源的 JNDI 名称为 `jdbc/consult`
JDBC 资源标识当前由此应用程序使用的连接池。
* JDBC 连接池。连接池指定数据库的连接详细信息,包括位置、用户名、口令。用于此应用程序的连接池为 `consultPool`
`glassfish-resources.xml` 文件中指定 JDBC 资源和连接池。通过在 "Projects"(项目)窗口中展开 "Server Resources"(服务器资源)节点并双击 `glassfish-resources.xml` 文件,可以在编辑器中打开此文件。此文件看起来类似于以下内容。
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
<jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false" connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10" connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-method="auto-commit" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-time-in-millis="60000" name="consultPool" non-transactional-connections="false" ping="false" pool-resize-quantity="2" pooling="true" res-type="javax.sql.DataSource" statement-cache-size="0" statement-leak-reclaim="false" statement-leak-timeout-in-seconds="0" statement-timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false">
<property name="serverName" value="localhost"/>
<property name="portNumber" value="3306"/>
<property name="databaseName" value="consult"/>
<property name="User" value="root"/>
<property name="Password" value="nb"/>
<property name="URL" value="jdbc:mysql://localhost:3306/consult?zeroDateTimeBehavior=convertToNull"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
/<jdbc-connection-pool>
<jdbc-resource enabled="true" jndi-name="jdbc/consult" object-type="user" pool-name="consultPool"/>
/<resources>
----
`glassfish-resources.xml` 中,您可以看到 JDBC 资源 `jdbc/consult` `consultPool` 标识为连接池的名称。您还可以看到 `consultPool` 的属性。在此应用程序中,只在 `glassfish-resources.xml` 中定义一个数据源和一个连接池。在某些情况下,您可能需要指定其他资源,例如,用于标识仅用于开发或测试的临时数据存储。
如果在运行应用程序时未自动在服务器上生成 JDBC 资源和连接池,您可以执行以下步骤在 GlassFish 管理控制台中手动创建这些资源。
1. 如果尚未打开 `glassfish-resources.xml`,请在编辑器中打开它。
创建 JDBC 资源和连接池时,将使用在 `glassfish-resources.xml` 中指定的属性值。
. "Services"(服务)窗口中右键单击 "GlassFish Server" 节点,然后在弹出式菜单中选择 "Open Domain Admin Console"(打开域管理控制台)在浏览器中打开 GlassFish 控制台。
. GlassFish 控制台的 "Common Tasks"(常见任务)导航面板中,展开 *JDBC* 节点以及 *JDBC Resources*(JDBC 资源)和 *JDBC Connection Pools*(JDBC 连接池)节点。
[.feature]
--
image::images/gf-admin-console-sm.png[role="left", link="images/gf-admin-console-lg.png"]
--
您可以看到当前注册到服务器的 JDBC 资源。如果在 "Common Tasks"(常见任务)导航面板中的 "JDBC" 节点下未列出 `jdbc/consult` `consultPool`,则您将需要创建它们。在安装服务器时,默认情况下创建了一些 JDBC 资源并且这些资源显示为子节点。
. 单击 *JDBC Connection Pools*(JDBC 连接池)节点,然后在 "New JDBC Connection Pool"(新建 JDBC 连接池)窗格中单击 "New"(新建)。
image::images/gf-new-jdbc-pool1.png[title="GlassFish 管理控制台中的 &quot;New JDBC Connection Pool&quot;(新建 JDBC 连接池)窗格"]
. 键入 *consultPool* 作为池名称,选择 *javax.sql.ConnectionPoolDataSource* 作为资源类型并选择 *MySql* 作为数据库驱动程序供应商。单击 "Next"(下一步)。
. 在步骤 2 中,定位并指定 *URL*、*username* *password* 属性的值。单击 "Finish"(完成)。
image::images/gf-new-jdbc-pool2.png[title="GlassFish 管理控制台中的 &quot;New JDBC Connection Pool&quot;(新建 JDBC 连接池)面板"]
可以在 `glassfish-resources.xml` 中查找这些属性的值。
单击 "Finish"(完成)时,将在服务器上创建新的连接池,并在 "JDBC Connection Pools"JDBC 连接池)节点下显示此连接池的节点。
. "Common Tasks"(常见任务)导航面板中单击 *JDBC Resources*(JDBC 资源)节点并单击 "New"(新建)。
. "JNDI Name"JNDI 名称)键入 *jdbc/consult*,并在 "Pool Name"(池名称)下拉列表中选择 *consultPool*。单击 "OK"(确定)。
image::images/gf-new-jdbc-resource.png[title="GlassFish 管理控制台中的 &quot;New JDBC Resource&quot;(新建 JDBC 资源)窗格"]
单击 "OK"(确定)时,将在服务器上创建新的 JDBC 资源,并在 "JDBC Resources"JDBC 资源)节点下显示此资源的节点。
IDE "Service"(服务)窗口中,可以在 "GlassFish Server" 下展开 "Resources"(资源)节点,您会看到 IDE 已添加新资源。您可能需要刷新视图(右键单击 "Resources"(资源)并选择 "Refresh"(刷新))来查看更改。
image::images/gf-services-jdbc-resources.png[title="在 IDE 的 &quot;Service&quot;(服务)窗口中显示的 JDBC 资源"]
有关使用 MySQL IDE 时的问题疑难解答的更多提示,请参见以下文档:
* link:../ide/mysql.html[+连接 MySQL 数据库+]教程。
* link:mysql-webapp.html#troubleshoot[+使用 MySQL 数据库创建简单的 Web 应用程序+]的“疑难解答”部分
link:/about/contact_form.html?to=3&subject=Feedback:%20Creating%20a%20JSF%202.0%20CRUD%20Application[+发送有关此教程的反馈意见+]
[[seealso]]
== 另请参见
有关 JSF 2.x 的详细信息,请参见以下资源。
=== NetBeans 文章和教程
* link:jsf20-intro.html[+NetBeans IDE 中的 JavaServer Faces 2.x 简介+]
* link:jsf20-support.html[+NetBeans IDE 中的 JSF 2.x 支持+]
* link:../../samples/scrum-toys.html[+Scrum 玩具 - JSF 2.0 完整样例应用程序+]
* link:../javaee/javaee-gettingstarted.html[+Java EE 应用程序入门指南+]
* link:../../trails/java-ee.html[+Java EE Java Web 学习资源+]
=== 外部资源
* link:http://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html[+JavaServer Faces 技术+](官方主页)
* link:http://jcp.org/aboutJava/communityprocess/final/jsr314/index.html[+JSR 314:JavaServer Faces 2.0 的规范+]
* Java EE 7 教程中的 link:http://docs.oracle.com/javaee/7/tutorial/doc/jsf-intro.htm[+JavaServer Faces 技术+]一章
* link:http://javaserverfaces.dev.java.net/[+GlassFish 项目 Mojarra+](JSF 2.x 的正式引用实现)
* link:http://forums.oracle.com/forums/forum.jspa?forumID=982[+OTN 论坛:JavaServer Faces+]
* link:http://www.jsfcentral.com/[+JSF 中心+]
=== 博客
* link:http://www.java.net/blogs/edburns/[+Ed Burns+]
* link:http://www.java.net/blogs/driscoll/[+Jim Driscoll+]