NetBeans 电子商务教程 - 添加实体类和会话 Bean

此页上的内容适用于 NetBeans IDE 版本 6.8 和 6.9

本单元介绍 Enterprise JavaBeans (EJB) 和 Java 持久性 (JPA) 技术。本教程中将使用 IDE 的两个向导,这两个向导对于 Java EE 开发是不可或缺的。它们是:

  • "Entity Classes from Database"(通过数据库生成实体类)向导:为每个选定的数据库表创建一个 Java 持久性 API 实体类,包含命名的查询标注、代表列的字段和代表外键的关系。
  • 实体类的会话 Bean 向导:使用基本的访问方法为每个实体类创建 EJB 会话 Facade。

这两个向导为快速设置应用程序模型提供了一条有效途径。如果重新检查您构建的应用程序的 MVC 图,可以看到 EJB 会话 Bean 和 JPA 实体类在其结构中的位置。

AffableBean 应用程序的 MVC 图

在本当单元中,创建的实体类可以形成了一个基于 Java 的 affablebean 数据库表示。每个实体类代表一个数据库表,而实体类的实例对应于可保存到(即保留)到数据库中的记录。应用程序的业务逻辑通过会话 Bean 进行封装,可以用作使 CRUD(Create-Read-Update-Delete,创建、读取、更新和删除)访问实体(如此处所演示)的 Facade,或者它们可以包含实现特定于应用程序的操作的代码。(此种情况的示例在第 9 单元:集成事务性业务逻辑中提供)。

您可以查看此教程中构建的应用程序的实时演示:NetBeans 电子商务教程演示应用程序



软件或资源 要求的版本
NetBeans IDE Java 包,6.8 或 6.9
Java 开发工具包 (JDK) 版本 6
GlassFish Server v3 或 Open Source Edition 3.0.1
MySQL 数据库服务器 版本 5.1
AffableBean 项目 快照 3

注:

  • NetBeans IDE 需要 Java 开发工具包 (JDK) 才能正常运行。如果不具备上面列出的任何资源,则应首先下载并安装 JDK。
  • NetBeans IDE Java 包包含在本教程中构建的应用程序所需的 Java Web 和 Java EE 技术。
  • NetBeans IDE Java 包还包含本教程所需的 GlassFish Server。可以单独下载 GlassFish Server,但是 NetBeans 下载中附带的版本具有在 IDE 中自动注册的额外好处。
  • 您可以按照本单元进程操作,而无需完成以前的单元。为此,请参见设置说明,该说明介绍如何准备数据库以及如何建立 IDE、GlassFish 和 MySQL 之间的连接。
  • AffableBean 项目的快照 4 可通过下载获得,并对应于使用 NetBeans IDE 6.9 完成此单元后的项目状态。

什么是 EJB 和 JPA 技术?

截至目前为止,您在本教程中开发的项目可以在具有 Servlet 容器的 Web 服务器中运行,如 Apache Tomcat。毕竟,目前您只使用了 JSTL 和 Servlet 技术,并使用 JDBC 直接连接到数据库。事实上,理论上可以继续使用这些技术开发应用程序,但需要为应用程序的各个方面进行手动编码,包括线程安全、事务和安全性。但是,通过使用具有 JPA 实体类的企业 Bean,可以依赖已经尝试和测试的解决方案,集中关注应用程序的业务逻辑。以下部分介绍两种技术,并定义它们在 EE 开发中的角色。

Enterprise JavaBeans

官方的 EJB 产品页将 EnterPrise JavaBeans 技术描述为“服务器端组件体系结构”,可用于“快速简便地开发分布式、事务性、安全且可移植的应用程序”。可以将 EJB(即,企业 Bean)应用到项目中,该项技术提供的服务对于开发人员来说仍然是透明的,从而消除了添加大量样板化代码这种冗长且通常容易出错的任务,否则必须要完成这些任务。如果对 EE 开发不熟悉,可能会质疑 EJB 在 Java web 应用程序中的必要性。由 Debu Panda、Reza Rahman 和 Derek Lane 撰写的书籍《EJB 3 实践》对 EJB 技术的使用进行了详细的解释:

虽然许多人认为用 EJB 开发相对简单的中型 Web 应用程序有些小题大做,但事实并非如此。建造房屋时,您不会一切都从头开始。而是根据需要购买材料,甚至购买承办商的服务。从头构建企业应用程序也是不切实际的。大多数服务器端应用程序有许多共同之处,包括改动业务逻辑、管理应用程序状态、存储和检索来自关系数据库的消息、管理事务、实现安全、执行异步处理和整合系统等。

作为一个框架,EJB 容器将这些通用功能以快捷服务的方式提供,以便 EJB 组件可以在应用程序中使用,而不必再浪费时间做无用功。例如,假定当您在 Web 应用程序中构建信用卡模块时,编写大量复杂而又容易出错的代码来管理事务和安全访问控制。通过使用 EJB 容器提供的声明性事务和安全服务,可以避免这些事情。在将这些服务及许多其他服务部署到 EJB 容器之后,它们即可用于 EJB 组件。这意味着编写高质量、功能丰富的应用程序可能要比想象的要快许多。
[1]

可以将 EJB 视为纳入到项目中的组件或 Java 类,以及提供许多与企业相关服务的框架《EJB 3 操作》介绍了本教程中使用的以下一些服务:

  • 入池:EJB 平台为每个 EJB 组件创建一个客户端共享的组件实例池。在任何时刻,每个池化实例只能由一个客户端使用。只要实例完成为客户端提供服务,它将返回到池,以便重新使用,而不是轻率地丢弃到垃圾回收器中进行回收。
  • 线程安全:EJB 使用完全不可见的方式确保所有组件线程安全并具有高性能。这意味着可以和开发单线程桌面应用程序一样编写服务器组件。无论组件本身的复杂程度如何,EJB 都可以确保其是线程安全的。
  • 事务:EJB 支持声明事务管理,可帮助使用简单的配置(而不是代码)将事务性行为添加到组件中。实际上,可以将任何组件方法指定为事务性组件。如果方法正常完成,EJB 将提交事务,使该方法对数据所做的更改永久生效。否则,将回退事务。容器管理 EJB 事务将在第 9 单元集成事务性的业务逻辑中进行介绍。
  • 安全性:EJB 支持与 Java 验证和授权服务 (JAAS) API 集成,因此可以使用简单配置轻松地全面呈现安全性,确保应用程序安全,而不必使用安全代码使应用程序过于混乱。[2] 第 11 单元保护应用程序说明了 EJB 的 <a href="http://download.oracle.com/javaee/6/api/javax/annotation/security/RolesAllowed.html" target="_blank"@RolesAllowed 标注。

Java 持久性

在 Java Enterprise 上下文中,持久性指的是自动将 Java 对象中包含的数据存储到关系数据库中的行为。Java 持久性 API (JPA) 是对象关系映射 (ORM) 技术,应用程序可以使用该技术以对开发者透明的方式管理 Java 对象和关系数据库间的数据。这意味着可以通过创建和配置镜像数据模型的 Java 类集(实体)来将 JPA 应用到项目中。然后,应用程序可以访问这些实体,如同直接访问数据库一样。

在项目中使用 JPA 有许多益处:

  • JPA 拥有自己丰富的、类似于 SQL 的查询语言,可以进行静态和动态查询。通过使用 Java 持久性查询语言 (JPQL),应用程序在不同的数据库供应商间仍保持可移植性。
  • 可以避免编写低级、冗长且容易出错的 JDBC/SQL 代码的任务。
  • JPA 以透明的方式为数据缓存和性能优化提供服务。

什么是会话 Bean?

客户端将调用企业会话 Bean 来执行特定的业务操作。名称会话意味着 Bean 实例可“工作单元”时间内使用。EJB 3.1 规范介绍了一个典型的会话对象,具有下列特性:

  • 代表单一客户端执行
  • 可识别事务
  • 更新底层数据库中的共享数据
  • 虽然可以访问和更新此数据,但不能直接代表数据库中的共享数据。
  • 时间相对较短
  • EJB 容器崩溃时会删除。客户端必须重新建立新的会话对象才能继续计算。

EJB 提供三种类型的会话 Bean:有状态无状态单件。以下介绍改编自《Java EE 6 教程》

  • 有状态:Bean 的状态将会保留在多个方法调用中。“状态”指的是实例变量的值。因为客户端与 Bean 进行交互,此状态经常调用会话状态。
  • 无状态:无状态 Bean 用于在单一方法调用中发生的操作。当方法完成了处理时,将不会保留 Bean 的客户端特定的状态。因此,无状态会话 Bean 不会保留客户端的会话状态。
  • 单件:单件会话 Bean 对每个应用程序实例化一次,并且在应用程序的生命周期中一直存在。单件会话 Bean 设计用于单一企业 Bean 实例在客户端间共享,并由客户端同时访问的环境。

有关 EJB 会话 Bean 的详细消息,请参见 Java EE 6 教程:什么是会话 Bean?

在本教程中,为了开发电子商务应用程序,我们仅使用无状态的会话 Bean。


关于规范和实现

下列规范对 EJB 和 JPA 技术进行了定义:

这些规范对技术进行了定义。但是,要将技术应用到项目中,必须使用规范的实现。当最终确定规范时,规范包括引用实现(技术的自由实现)。如果发现此概念比较容易混淆,请考虑以下的类比:乐曲(即某页上的音符)定义一首曲子。音乐家学习曲子并对演奏进行录音相当于对曲子进行解释。这种情况下,乐曲类似于技术规范,音乐家的录音对应于规范的实现。

有关 Java 技术规范的解释以及如何对其进行正式标准化,请参见什么是 Java Community Process?

如果检查 EJB 和 JPA 规范最终发行版本的下载页,则可以找到以下引用实现的链接:

JPA 规范的实现称为持久性提供器,而 EclipseLink 是选作 JPA 2.0 规范引用实现的持久性提供器。

如果检查 EJB 引用实现的链接,达到的页面不但列出 EJB 的实现,而且还列出项目 GlassFish 提供的所有引用实现。这样设计的原因是,Project GlassFish 形成 Java EE 6 平台规范 (JSR 316) 的引用实现。GlassFish v3 应用服务器或 Open Source Edition(在此教程中用于构建电子商务项目)包含根据 Project GlassFish 开发的所有技术的引用实现。同样地,这称为 Java EE 6 容器

Java EE 容器包含三个重要的组件:Web(即,Servlet)容器、EJB 容器和持久性提供器。下图中显示了电子商务应用程序的部署方案。在本单元中创建的实体类由持久性提供器管理。在本单元中创建的会话 Bean 由 EJB 容器管理。视图显示在 JSP 页中,由 Web 容器进行管理。

GlassFish v3 Java EE 容器

添加实体类

首先,使用 IDE 的 "Entity Classes from Database"(通过数据库生成实体类)向导生成基于 affablebean 方案的实体类。向导依靠基本持久性提供器来完成此任务。

  1. 在 IDE 中打开项目快照。在 IDE 中,按 Ctrl-Shift-O 组合键(在 Mac 上为 �-Shift-O 组合键),然后导航至计算机上解压缩下载文件的位置。
  2. 按 Ctrl-N 组合键(在 Mac 上为 ⌘-N 组合键)打开文件向导。
  3. 选择 "Persistence"(持久性)类别,然后选择 "Entity Classes from Database"(通过数据库生成实体类)。单击 "Next"(下一步)。
  4. 在“步骤 2:Database Tables(数据库表)”中,从 "Data Source"(数据源)下拉列表中选择 jdbc/affablebean。与应用服务器中注册的数据源将填充下拉列表。

    当选择 jdbc/affablebean 数据源时,IDE 对数据库进行扫描,并且在 "Available Tables"(可用表)窗格中列出数据库表。
    “通过数据库生成实体类”向导
  5. 单击 "Add All"(全部添加)按钮,然后单击 "Next"(下一步)。
  6. “通过数据库生成实体类”向导的步骤 3 在 NetBeans IDE 6.8 与 6.9 之间略有不同。根据所使用的 IDE 版本,相应执行以下步骤。

    NetBeans IDE 6.8

    “通过数据库生成实体类”向导,步骤 3:
    1. 在 "Package"(包)字段中键入 entity。在完成时,向导将为实体类创建一个新的包。
    2. 单击 "Create Persistence Unit"(创建持久性单元)按钮。"Create Persistence Unit"(创建持久性单元)对话框打开。

      持久性单元是指应用程序中存在的实体类集合。上述对话框生成了 persistence.xml 文件,持久性提供器使用该文件指定持久性单元的配置设置。注:"EclipseLink (JPA 2.0)" 是与项目相关的服务器的默认选项。将 "Table Generation Strategy"(表生成策略)集合设置为 None(无)。这可以防止持久性提供器影响数据库。(例如,如果希望删除持久性提供器,然后重新创建基于现有实体类的数据库,则可以将策略设置为 Drop and Create(删除并创建)。每次部署项目时,都会使用该操作。)
    3. 单击 "Create"(创建)。
    4. 返回到“步骤 3:Entity Classes(实体类)”中,注意实体的类名称是基于数据表。例如,CustomerOrder 实体将映射到 customer_order 数据库表中。同时注:在默认情况下,已选中 "Generate Named Query Annotations for Persistent Fields"(为持久性字段生成已命名的查询批注)。我们将在本教程的以后部分使用不同的命名查询。
    5. 继续执行下面的步骤 7

    NetBeans IDE 6.9

    “通过数据库生成实体类”向导,步骤 3:
    1. 在 "Package"(包)字段中键入 entity。在完成时,向导将为实体类创建一个新的包。
    2. 注意以下几点:
      • 实体的类名是基于数据库表的。例如,CustomerOrder 实体将映射到 customer_order 数据库表。
      • 默认情况下,"Generate Named Query Annotations for Persistent Fields"(为持久性字段生成已命名的查询批注)选项处于选中状态。我们将在本教程的以后部分使用不同的命名查询。
      • 默认情况下,"Create Persistence Unit"(创建持久性单元)选项处于选中状态。持久性单元是应用程序中存在的实体类集合。持久性单元是由 persistence.xml 配置文件定义的,该文件可供持久性提供器读取。因此,启用此选项意味着,该向导还将生成 persistence.xml 文件,并使用默认设置对其进行填充。
  7. 单击 "Finish"(完成)。将生成基于 affablebean 数据库表的 JPA 实体类。通过展开新创建的 entity 包,可以在 "Projects"(项目)窗口中检查实体类。另请注意,新的持久性单元位于 "Configuration Files"(配置文件)节点的下方。


    注:向导将生成一个其他的实体类 OrderedProductPK。回想一下数据模型的 ordered_product 表使用由 customer_orderproduct 表的主键组成的复合主键。(请参见设计数据模型 - 创建多对多关系。)为此,持久性提供器为复合键创建单独的实体类,并且将其嵌入OrderedProduct 实体中。可以在编辑器中打开 OrderedProduct 以检查该实体。JPA 使用 @EmbeddedId 标注来表示该可嵌入类是复合主键。
    public class OrderedProduct implements Serializable {
        private static final long serialVersionUID = 1L;
        @EmbeddedId
        protected OrderedProductPK orderedProductPK;

    @EmbeddedId 标注上按 Ctrl-空格组合键以调用 API 文档。

    对 @EmbeddedId 调用的 API 文档
  8. 在编辑器中打开持久性单元 (persistence.xml)。除了 XML 视图之外,IDE 还为持久性单位提供 "Design"(设计)视图。"Design"(设计)视图为项目持久性提供器管理的配置更改提供一个便利的方法。
    AffableBeanPU 持久性单元的设计视图
  9. 单击 AffableBeanPU 持久性单元顶部的 XML 标签以打开 XML 视图。在文件中添加以下属性。
    <persistence-unit name="AffableBeanPU" transaction-type="JTA">
      <jta-data-source>jdbc/affablebean</jta-data-source>
      <properties>
        <property name="eclipselink.logging.level" value="FINEST"/>
      </properties>
    </persistence-unit>
    将日志记录级别属性设置为 FINEST,以便在应用程序运行时,可以查看由持久性提供器生成的所有可能的输出。这样,便可以查看正在数据库上使用的持久性提供器的 SQL,并可以帮助进行任何所需的调试。

    有关日志记录的说明和所有日志记录值的列表,请参见官方 EclipseLink 文档:如何配置日志记录


添加会话 Bean

在此部分,我们使用 IDE 的实体类的会话 Bean 向导为刚才创建的每个实体类生成 EJB 会话 Facade。每个会话 Bean 将包含其各自实体类的基本访问方法。

会话 FacadeEnterprise BluePrints 程序中发布的设计模式。如核心 J2EE 模式目录中所述,将尝试解决多层应用程序环境中引起的常见问题,例如:

  • 紧密耦合,将导致客户端和业务对象间出现直接依赖关系
  • 客户端和服务器间调用方法过多,将导致网络性能问题
  • 缺乏统一的客户端访问策略,将使业务对象滥用

会话 Facade 抽象化基础业务对象交互,提供仅公开所需功能的服务层。因此,它隐藏了客户端视图中参与者之间复杂的交互。因此,会话 Bean(代表会话 Facade)管理业务对象之间的关系。会话 Bean 还通过根据工作流的要求,创建、定位、修改和删除参与者的生命周期来对其进行管理。

  1. 按 Ctrl-N 组合键(在 Mac 上为 ⌘-N 组合键)打开文件向导。
  2. 选择 "Persistence"(持久性)类别,然后选择 "Session Beans for Entity Classes"(实体类的会话 Bean)。
    文件向导:
  3. 单击 "Next"(下一步)。
  4. 在“步骤 2:Entity Classes(实体类)”中,请注意项目中包含的所有实体类都列在 "Available Entity Classes"(可用的实体类)的左下方。单击 "Add All"(全部添加)。所有实体类都将移至 "Selected Entity Classes"(选定的实体类)右下方。
  5. 单击 "Next"(下一步)。
  6. 在步骤 3:生成的会话 Bean 中,在 "Package"(包)字段中键入 session
    实体类的会话 Bean 向导 - 步骤 3:

    注:可以使用该向导为会话 Bean 生成本地接口和远程接口。虽然将会话 Bean 编程为接口有一定的益处(例如,通过将业务对象交互隐藏在接口后面,可以进一步将客户端与业务逻辑分离。这还意味着可以在需要时为应用程序接口的多个实现编写代码),便这并不在教程讨论的范围之内。注:EJB 3.1 之前的版本需要为每个会话 Bean 实现一个接口。

  7. 单击 "Finish"(完成)。IDE 将为项目中包含的每个实体类生成会话 Bean。在 "Projects"(项目)窗口中,展开新的 session 包,检查会话 Bean。

    NetBeans 6.8 NetBeans 6.9

    注:如上所示,在 NetBeans IDE 6.9 中,实体类的会话 Bean 向导生成 Facade 类的方式略有改进。换而言之,将所有类共用的样板化代码提取到名为 AbstractFacade 的抽象类中。如果使用的是版本 6.9,请打开已生成的任何 Facade 类(AbstractFacade 除外)。您将会看到该类是对 AbstractFacade 的扩展。

  8. 在编辑器中打开一个会话 Facade,例如 ProductFacade。所有生成的会话 Facade 都使用 @PersistenceContext 标注实例化 EntityManager
    @PersistenceContext(unitName = "AffableBeanPU")
    private EntityManager em;
    @PersistenceContext 标注用于将容器管理的 EntityManager 注入类中。换而言之,我们依赖 GlassFish 的 EJB 容器,根据需要打开和关闭 EntityManagerunitName 元素用于指定 AffableBeanPU 持久性单元,该持久性单元已在应用程序的 persistence.xml 文件中定义。

    EntityManager 是 Java 持久性 API 的有机组成部分,负责在数据库上执行持久性操作。《EJB 3 实践》EntityManager 的介绍如下所示:
    JPA EntityManager 接口通过实际提供持久性服务管理实体。虽然实例告诉 JPA 提供器其映射到数据库的方式,但它们不会保存其本身。EntityManager 接口读取实体的 ORM 元数据,并执行持久性操作。

现在,应用程序包含 affablebean 数据库的 JPA 实体类型形式的持久性模型。它还包括一个由可用于访问实体类的企业 Bean 构成的会话 Facade。下一部分演示如何使用会话 Bean 和实体类访问数据库。


使用 EJB 访问数据

上一教程单元中,学习了如何通过在 GlassFish 上配置数据源,将资源引用添加到应用程序的部署描述符中,以及使用应用程序 JSP 页中的 JSTL <sql> 标记,来从应用程序访问数据库。这是一个重要的技术,因为通过它可以快速设置包含数据库中数据的原型。然而,对大中型应用程序或由开发团队管理的应用程序来说,这一方案并不现实,因为事实证明维护或扩展并非易事。此外,如果开发多层应用程序,或遵循 MVC 模式,您可能不想在前端中保留数据访问代码。通过结合持久性模型使用企业 Bean,可以有效地分离表示和模型组件,从而更好地符合 MVC 模式。

以下说明演示了如何在 AffableBean 项目中开始使用会话和实体 Bean。将删除之前为索引页和类别页所设置的 JSTL 数据访问逻辑。在其位置,将使用会话 Bean 提供的数据访问方法,并将数据存储在作用域变量中,以便可以从前端页视图中对其进行检索。我们首先处理索引页,然后转到更为复杂的类别页。

索引页

索引页需要四个产品类的数据。在我们当前设置中,每次请求索引页时 JSTL <sql> 标记都在数据库中查询类别详细信息。由于很少对此信息进行修改,因而从性能角度看,只有在应用程序部署之后执行一次查询并将数据存储在应用程序作用域的属性中才更有意义。我们可以通过将此代码添加到 ControllerServletinit 方法中来完成此操作。

  1. 在 "Projects"(项目)窗口中,双击 "Source Packages"(源包)> controller > ControllerServlet 节点以在编辑器中将其打开。
  2. 声明 CategoryFacade 的实例,并对该实体应用 @EJB 标注。
    public class ControllerServlet extends HttpServlet {
    
        @EJB
        private CategoryFacade categoryFacade;
    
        ...
    }
    @EJB 标注指示 EJB 容器使用命名为 CategoryFacade 的 EJB 来实例化 categoryFacade 变量。
  3. 使用 IDE 的提示来为以下项添加导入语句:
    • javax.ejb.EJB
    • session.CategoryFacade

    按 Ctrl-Shift-I 组合键(在 Mac 上为 ⌘-Shift-I 组合键)自动将所需导入添加到类中。

  4. 将以下 init 方法添加到类中。Web 容器通过调用其 init 方法初始化 Servlet。这只在载入 Servlet 之后以及开始服务请求之前发生一次。
    public class ControllerServlet extends HttpServlet {
    
        @EJB
        private CategoryFacade categoryFacade;
    
        public void init() throws ServletException {
    
            // store category list in servlet context
            getServletContext().setAttribute("categories", categoryFacade.findAll());
        }
    
        ...
    }
    此处,应用 Facade 类的 findAll 方法,以在数据库中查询 Category 的所有记录。然后,将 Category 对象的结果 List 设置为 "categories" 字符串引用的属性。将引用置于 ServletContext 中意味着引用存在于应用程序范围内的作用域内。

    要快速确定 findAll 方法的方法签名,请将鼠标悬停在该方法上,同时按下 Ctrl 键(在 Mac 上为 ⌘)。(下图显示了使用 NetBeans IDE 6.8 时出现的弹出式窗口。)

    在弹出式窗口中显示方法签名的编辑器
    单击超链接可以直接导航到该方法。
  5. 使用 IDE 的提示添加 @Overrides 标注。init 方法是通过 HttpServlet 的超类 GenericServlet 定义的。
    显示在编辑器中的提示
    添加标注不是必需的,但是,它提供了若干优点:
    • 通过它可使用编译器检查确保实际覆盖某个假定要覆盖的方法。
    • 它可以提高可读性,因为当覆盖源代码中的方法时,就会变得清楚。

    有关标注的详细信息,请参见 Java 教程:标注

  6. 现在已经设置了包含类别列表的应用程序范围内的属性,请修改索引页以访问新创建的属性。

    在 "Projects"(项目)窗口中的双击 "Web Pages"(Web 页)> index.jsp 节点以在编辑器中打开该文件。
  7. 注释掉(或删除)列在文件顶部的 <sql:query> 语句。要在编辑器中注释掉代码,请突出显示该代码,然后按 Ctrl-/ 组合键(在 Mac 上为 ⌘-/ 组合键)。
    显示在编辑器中的已注释掉片段
  8. 修改打开的 <c:forEach> 标记,以便 items 属性引用新的应用程序范围内的 categories 属性。
    <c:forEach var="category" items="${categories}">
  9. 打开项目的 Web 部署描述符。按 Alt-Shift-O 组合键(在 Mac 上为 Ctrl-Shift-O 组合键)并且在 "Go to File"(转至文件)对话框中,键入 "Web",然后单击“确定”。
  10. 注释掉(或删除)<resource-ref> 条目。<sql> 标记需要使用此条目来标识服务器上注册的数据源。现在,我们依靠 JPA 访问数据库,并且已在持久性单元中指定了 jdbc/affablebean 数据源。(请参见上文的项目持久性单元的 "Design"(设计)视图。)

    突出显示整个 <resource-ref> 条目,然后按 Ctrl-/ 组合键(在 Mac 上为 ⌘-/ 组合键)。
    <!-- <resource-ref>
             <description>Connects to database for AffableBean application</description>
             <res-ref-name>jdbc/affablebean</res-ref-name>
             <res-type>javax.sql.ConnectionPoolDataSource</res-type>
             <res-auth>Container</res-auth>
             <res-sharing-scope>Shareable</res-sharing-scope>
         </resource-ref> -->
  11. 运行项目。单击 "Run Project"(运行项目)() 按钮。项目的索引页在浏览器中打开,您可以看到所有四个类别名称和图像都显示出来。
    显示类别详细信息的索引页

类别页

为了正确显示,类别页需要三条数据:

  1. 类别数据:适用于左列类别按钮
  2. 选定的类别:在左侧列中突出显示选定的类别,并且选定的类别名称显示在产品表上方
  3. 选定类别的产品数据:适用于产品表中显示的产品

我们单独分析每条数据。

类别数据

为了说明类别数据,我们可以重新使用为索引页创建的应用程序范围内的 categories 属性。

  1. 在编辑器中打开 category.jsp,然后注释掉(Ctrl-/;在 Mac 上为 ⌘-/)在文件顶部列出的 JSTL <sql> 语句。
    编辑器中已注释掉的 <sql> 语句
  2. 修改打开的 <c:forEach> 标记,以便 items 属性引用应用程序范围内 categories 属性。(这与上面对 index.jsp 的操作相同。)
    <c:forEach var="category" items="${categories}">
  3. 运行项目以检查类别页的当前状态。单击 "Run Project"(运行项目)() 按钮。当在浏览器中打开项目的索引页时,单击四个类别中的任何一个。左列中的类别按钮按预期显示和运行。
    在左栏中显示类别按钮的类别页

选定的类别

要检索选定的类别,我们可以使用已经创建的 categoryFacade 查找 ID 与请求查询字符串相匹配的 Category

  1. 在编辑器中打开 ControllerServlet。(如果已经打开,请按 Ctrl-Tab 组合键,并从弹出式列表中进行选择。)
  2. 启动实现功能以获得选定的类别。找到 TODO: Implement category request 注释并将其删除,然后添加以下代码(以粗体显示)。
    // if category page is requested
    if (userPath.equals("/category")) {
    
        // get categoryId from request
        String categoryId = request.getQueryString();
    
        if (categoryId != null) {
    
        }
    
    // if cart page is requested
    } else if (userPath.equals("/viewCart")) {
    通过对请求调用 getQueryString(),可以检索请求的类别 ID。

    注:在左列类别按钮中,确定所选定类别的逻辑已在 category.jsp 中使用 EL 表达式实现,这种表达式相当于在 Servlet 中调用 getQueryString()。EL 表达式是:pageContext.request.queryString

  3. if 语句内添加以下代码行。
    // get categoryId from request
    String categoryId = request.getQueryString();
    
    if (categoryId != null) {
    
        // get selected category
        selectedCategory = categoryFacade.find(Short.parseShort(categoryId));
    }
    根据请求的类别 ID,使用 CategoryFacadefind 方法检索 Category 对象。请注意,必须将 categoryId 强制转换为 Short,因为这是用于 Category 实体类中 id 字段的类型。
  4. 单击左旁注中的标记 (提示标记) 以使用编辑器提示在 doGet 方法中将 selectedCategory 声明为一个局部变量。
    编辑器提示
    因为 selectedCategory 属于 Category 类型(尚未导入类中),所以,IDE 会将 entity.Category 的 import 语句自动添加到文件的顶部。
  5. 添加下列行以将检索的 Category 对象置入请求作用域中。
    // get categoryId from request
    String categoryId = request.getQueryString();
    
    if (categoryId != null) {
    
        // get selected category
        selectedCategory = categoryFacade.find(Short.parseShort(categoryId));
    
        // place selected category in request scope
        request.setAttribute("selectedCategory", selectedCategory);
    }
  6. 在编辑器中,切换到 category.jsp。(按 Ctrl-Tab 组合键,并从弹出式列表中选择。)
  7. 找到 <p id="categoryTitle">,进行以下更改。
    <p id="categoryTitle">
        <span style="background-color: #f5eabe; padding: 7px;">${selectedCategory.name}</span>
    </p>
    现在使用的是 selectedCategory 属性,即刚刚从 ControllerServlet 添加到请求作用域的属性。在 EL 表达式内使用 ".name" 时,会对给定 Category 对象调用 getName 方法。
  8. 切换回浏览器,并刷新类别页。现在,选定的类别的名称显示在页面中。
    显示所选类别名称的类别页

选定类别的产品数据

为了检索选定类别的所有产品,我们将利用 Category 实体的 getProductCollection() 方法。首先对 selectedCategory 调用此方法,以获取与 selectedCategory 相关联的所有 Product 的集合。然后,将该产品集合作为属性存储在请求作用域中,最后从 category.jsp 页视图中引用该作用域属性。

  1. ControllerServlet 中,将以下语句添加到管理类别请求的代码中。
    // if category page is requested
    if (userPath.equals("/category")) {
    
        // get categoryId from request
        String categoryId = request.getQueryString();
    
        if (categoryId != null) {
    
            // get selected category
            selectedCategory = categoryFacade.find(Short.parseShort(categoryId));
    
            // place selected category in request scope
            request.setAttribute("selectedCategory", selectedCategory);
    
            // get all products for selected category
            categoryProducts = selectedCategory.getProductCollection();
        }
    此处,调用 getProductCollection() 可使我们获取与 selectedCategory 相关联的所有 Product 的集合。
  2. 使用编辑器的提示,将 categoryProducts 定义为 doGet 方法的局部变量。
    编辑器提示
  3. Product 集合置于请求作用域中,以便从应用程序的前端对其进行检索。
    // if category page is requested
    if (userPath.equals("/category")) {
    
        // get categoryId from request
        String categoryId = request.getQueryString();
    
        if (categoryId != null) {
    
            // get selected category
            selectedCategory = categoryFacade.find(Short.parseShort(categoryId));
    
            // place selected category in request scope
            request.setAttribute("selectedCategory", selectedCategory);
    
            // get all products for selected category
            categoryProducts = selectedCategory.getProductCollection();
    
            // place category products in request scope
            request.setAttribute("categoryProducts", categoryProducts);
        }
  4. 在编辑器中打开 category.jsp 文件,并对 product 表做出下列更改。
    <table id="productTable">
    
        <c:forEach var="product" items="${categoryProducts}" varStatus="iter">
    现在 <c:forEach> 标记可以引用 categoryProducts 集合。现在,c:forEach 循环将对该集合中包含的每个 Product 对象进行迭代,并且相应地提取数据。
  5. 按 F6 键(在 Mac 上为 fn-F6 组合键)运行项目。在浏览器中导航至类别页,并注意到现在将为每种类别显示所有产品。
    在表中显示产品的类别页

本单元简要地介绍了 JPA 和 EJB 技术。同时还介绍 Java 规范的角色,以及 GlassFish 应用服务器如何使用引用实现。其次,演示如何创建提供项目数据库的 Java 实现的 JPA 实体类集。然后,按照会话 Facade 模式,演示如何创建 EJB 会话 Bean 集,这些 Bean 存在于实体类的顶部,并且可以更方便地对其进行访问。最后,修改 AffableBean 项目以利用新的会话 Bean 和实体进行索引页和类别页所需的数据库访问。

可以下载 AffableBean 项目的快照 4,它对应于使用 NetBeans IDE 6.9 完成此单元之后的项目状态。

在下一个单元中,将探讨会话管理,以及在用户浏览站点时,如何使应用程序记住用户的操作。这是在电子商务应用程序中,实现购物车机制的关键。



另请参见


参考

  1. ^ 改编自《EJB 3 操作》第 1 章,1.1.2 部分:EJB 作为一个框架。
  2. ^EJB 提供许多其他的服务。有关更全面的列表,请参见《EJB 3 实践》,第 1 章,1.3.3 部分:使用 EJB 服务获得功能。