使用 Apache OpenJPA 开发 EJB 3.0 应用(上)

第 1 部分: OpenJPA 与 EJB 3.0

几乎所有的企业应用都需要持久化数据,没有数据持久化需求的企业应用在现在的市场环境下几乎是不可能出现的。由于关系型数据的普及,通常我们提到数据持久化时,一般指的是将数据持久化到关系型数据库中。关系型数据是一种结构化的数据管理方式,开发者只能通过 SQL 来操作数据库。
Java 语言天生就是一门面向对象的编程语言,在 Java 的世界中,被处理的内容都被组织成一个一个的对象,对象和对象之间存在着继承、引用关系,这样的关系无法通过简单的方式直接反应到关系型数据库中。因此在关系型数据库与面向对象之间便存在着阻抗失谐(impedance mismatch)。

 

  • 发展中的持久化技术
  1. 序列化
    序列化是最早出现的、管理持久化数据的实现方案,也是 Java 语言中内置的数据持久化解决方案。它的工作原理是将对象转化为字节流,生成的字节流能够通过网络传输或者保存在文件中。序列化非常易于使用,但是局限性也非常大,由于序列化必须一次将所有对象全部取出,这限制了它在处理大量数据情形下的应用,同时它也无法在更新失败的情况下撤销对对象的修改,这使它无法用于对数据一致性要求严格的应用中。多线程或者多个应用不能同时并发地、互不冲突地读写同一个序列化数据,也不能提供查询功能。
  2. JDBC
    很多企业应用的开发者选择使用 JDBC 管理关系型数据库中的数据。相对序列化而言,JDBC 克服了很多缺点:它支持处理大量的数据,能够保证数据的一致性,支持信息的并发访问,提供 SQL 查询语言查找数据。不幸的是,JDBC 没有提供序列化所具有的易用性。JDBC 所使用的关系模型不是为保存对象而设计的,因此迫使开发者选择在处理持久数据时放弃面向对象编程,或者自己去开发将面向对象特性(比如:类之间的继承)和关系型数据库进行映射的专有解决方案。
  3. 关系对象映射(Object Relational Mapping,ORM)
    ORM 是目前完成对象和关系数据表之间的映射最好的一种技术, 这些 ORM 框架处理对象和关系数据库之间的协调工作,将开发者从这部分工作中解脱出来,集中精力处理对象模型。阻碍 ORM 发展的问题是,现有的每一种 ORM 产品都有自己特有的 API,开发者只能将自己的代码绑定到某一个框架提供商的接口上,这种状况形成了厂商锁定,意味着一旦该框架提供商无法解决系统中出现的严重错误,或者因为其它的原因转而采用其它的框架,将会给开发者的企业应用带来极大的困难,唯一的解决办法是重写所有的持久化代码。
  4. 对象数据库(Object DataBase)
    已经有一些软件公司选择了开发为保存对象而特别设计的对象数据库,而不是选择将对象映射到关系型数据库上。这种解决方案通常比使用对象/关系映射更加易于使用。和 ORM 相同的问题是,对象数据库的访问接口并没有标准化,因此非常容易形成厂商锁定的局面。与此同时,放弃已经成熟的关系数据库而转向未知的对象数据库让非常多的企业决策者犹豫不决。而且目前为对象数据库而设计的分析工具太少,无法满足企业的需求。而且现实情况下,每一个企业基本上都有大量的已有数据保存在关系数据库中,要从关系数据库转向对象数据库对企业而言也需要大量工作。
  5. EJB 2.X
    EJB 2.X 实体 Bean 是管理持久化数据的组件框架,和 ORM 解决方案一样,EJB 2.X 实体 Bean 提供持久化数据的面向对象视图。和 ORM 解决方案不一样的是,EJB 2.X 实体 Bean 不仅仅局限于数据库,它展示的信息可能来自于 EIS(Enterprise Information System)或者其他持久化设备。EJB 2.X 实体 Bean 最大的局限是规定了太过于严格的标准,这些标准保证了企业应用能够在不同的 EJB 容器之间可以移植,但是也让 EJB2.X 实体 Bean 规范变得非常复杂并难于使用。而且 EJB 2.X 标准在面向对象特性处理方面的支持非常有限,无法支持继承、多态和复杂关系等面向对象的高级特性。EJB 2.X 实体 Bean 只能在重量级的、价格昂贵的 EJB 容器中运行,这对应用 EJB 2.X 实体 Bean 开发企业应用提出了更高的要求,加重了企业的经济压力。
  6. Java 数据对象(Java Data Object,JDO)
    JDO 是 Java EE 标准中另外一个支持管理持久化数据的规范,JDO 规范使用和 JPA 非常类似的 API,只是通常是通过 JCA 技术集成到应用服务器上。但是 JDO 是针对轻量级容器而设计的,不能够支持容器级别的声明式安全、事务特性,也无法对远程方法调用提供支持。
  •  EJB 3.0 规范

EJB 3.0 规范由三部分组成:EJB3.0 Simplified API、EJB 核心规范(EJB Core Contracts and Requirements)和 JPA(Java Persistence API)。

  1. Simplified API
    Simplified API 部分主要规定了基于 EJB 3.0 标准开发企业应用时所需要遵守的 Bean 类和接口要求、这些 API 的使用方式以及容器支持等多方面的内容。还详细的规定了 EJB3.0 中除 Java Persistence API 部分之外的 EJB 实现所支持的注释(Annotation)。规范中还有专门章节讲解 EJB 3.0 和此前的 EJB 规范如何同时工作,以及如何将此前已经开发好的企业应用移植到 EJB 3.0 容器中。其中的 Persistence 的内容放在了 JPA 规范中。
  2. EJB 核心规范
    EJB 核心规范中首先描述了 EJB 在企业应用中的角色、EJB 规范的体系结构,确定了支持 EJB 标准的容器应该遵守的准则和要求。随后从多个角度详细的介绍了 EJB 体系中各部分的功能需求和实现要求,包括 Session Bean、消息驱动 Bean(Message-Driven Bean)、事务、安全管理、部署描述符等。其中的 Persistence 的内容放在了 JPA 规范中。由于 EJB 3.0 规范并不排斥之前的 EJB 规范,因此 EJB 2.X 和 EJB 1.X 中的内容也保留在了 EJB 核心规范中。
  3. Java Persistence API(JPA)
    EJB 2.X 和 EJB 1.X 规范中的实体 Bean(EntityBean)部分都难以使用,使持久化成为 EJB 规范的一个软肋,影响了 EJB 标准发挥更大的作用,自然而然的,JPA 成为了 EJB3.0 规范中被关注最多的部分。JPA 规范部分详细的介绍了 JPA 中实体 Bean 新的定义,并介绍了实体 Bean 支持的注释、全新的查询语言、实体管理接口、容器实现规范等内容。


JPA 标准中引入了新的实体概念,每一个实体都是一个普通的 Java 类,不需要继承任何其他的接口或者扩展某个指定类,这个 Java 类必须使用 javax.persistence.Entity 进行注释。JPA 标准中还提供了包括 javax.persistence.Table、javax.persistence.Id 等在内的多个注释,用于完成实体和数据库之前的映射。JPA 中引入了新的查询语言 JPQL(Java Persistence Query Language),JPQL 允许开发者采用面向对象的查询语言来查找实体,这些实体持久化在关系型的数据库中,”select a from Animal a where a.name=’a’”是一个 JPQL 的例子。其中的 Animal 是一个 Java 类,而不是关系型数据库中的一个表或者视图。除了简单的查询功能之外,JPQL 中还能够支持 Group、Order 等通常只有 SQL 才能提供的高级功能。JPA 标准中还规定了在 Java EE 环境中和非 Java EE 环境中使用 JPA 时的差异,以及 Java EE 环境中容器的职责等。

  

  • JPA 体系架构

JPA 中定义一套类和接口用于实现持久化管理和对象/关系的映射,下面这张图中显示了 JPA 的主要组件以及它们之间的相互关系。

  1.  EntityManagerFactory
    EntityManagerFactory 是 EntityManager 的工厂类,负责创建 EntityManager 对象。
    ? EntityManager
    EntityManager 是 JPA 应用中使用的基本对象,通过它提供的相应方法可以管理持久化对象,也可以新建或者删除持久化对象。EntityManager 还负责创建 Query 实例。在容器外使用时,EntityManagerFactory 和 EntityManager 之间是一对一的关系。
  2. Entity
    EntityTransaction 提供 Entity 操作时需要的事务管理,和 EntityManager 是一对一的关系。在查询操作时不需要使用 EntityTransaction,而在对象持久化、状态更新、对象删除等情况下则必须使用显式的使用 EntityTransaction 的相关方法管理事务。
  3. Query
    Query 是查询实体的接口,Query 对象可以从 EntityManager 中获得。根据 EJB 3.0 规范中的描述,Query 接口需要同时支持 JPQL 和原生态 SQL 两种语法。
  4. Persistence
    Persistence 是一个工具类,负责根据配置文件提供的参数创建 EntityManagerFactory 对象。
    下面的代码演示了如何通过 JPA 提供的接口和 JPQL 查询语言完成实体查询和更新的例子,例子中的代码假定运行在非 Java EE 环境中。

清单 1 在非 Java EE 环境使用 JPA 接口的例子
               
1. /*
2. * Persistence 类获取 EntityManagerFactory 实例;
3. * 一般 EntityManagerFactory 实例被缓存起来重复使用,
4. * 避免重复创建 EntityManagerFactory 实例引起的性能影响
5. */
6.   EntityManagerFactory factory =
7.       Persistence.createEntityManagerFactory (“mysql”);
8. 
9.   // 从 EntityManagerFactory 实例 factory 中获取 EntityManager
10.   EntityManager em = factory.
11.       createEntityManager(PersistenceContextType.EXTENDED);
12. 
13.   // 实体的更新需要在事务中运行
14.   EntityTransaction tx = em.getTransaction ();
15.   tx.begin ();
16. 
17.   // 查找所有公司中的女性雇员
18.   Query query = em.createQuery ("select e from Employee e "  
19.       + " where e.sex = 'femail'");
20.   List results = query.getResultList ();
21. 
22.   // 给所有女性雇员增加半天假期
23.   for (Object res : results){   
24.       Employee emp = (Employee) res;   
25.       emp.setHoliday (emp.getHoliday () +0.5);}
26. 
27.   // 提交事务(持久化所有更新)
28.   tx.commit ();
29.   em.close ();
30.   factory.close ();

下面的代码显示了在 EJB 容器中开发 JPA 应用时的接口使用情况,由于容器中的 EntityManager 是注入的,事务也是声明式的,因此在容器中完成上面的业务逻辑要简单得多。

清单 2 在容器中运行的 JPA 例子
               
1.   /*
2.    * 在容器中运行 JPA 应用时,EntityManager 接口的实例”em”
3.    * 是通过 @Resource 注释注入的。事务也通常是声明式的。
4.    */
5.   // 查找所有公司中的女性雇员
6.   Query query = em.createQuery ("select e from Employee e "  
7.       + " where e.sex = 'femail'");
8.   List results = query.getResultList ();
9. 
10.   // 给所有女性雇员增加半天假期
11.   for (Object res : results){   
12.       Employee emp = (Employee) res;   
13.       emp.setHoliday (emp.getHoliday () +0.5);}

 
 

  •  JPA 的优势

JPA 标准制定过程中充分吸收了目前已经出现的所有持久化技术的所有优点,摒弃了它们存在的局限,使 JPA 在简单易用、查询能力等方面表现突出。

  1. 标准化
    JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问 API,这保证了基于 JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
  2. 对容器级特性的支持
    JPA 框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
  3. 简单易用,集成方便
    JPA 的主要目标之一就是提供更加简单的编程模型:在 JPA 框架下创建实体和创建 Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity 进行注释;JPA 的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA 基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
  4. 可媲美 JDBC 的查询能力
    JPA 定义了独特的 JPQL(Java Persistence Query Language),JPQL 是 EJB QL 的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
  5. 支持面向对象的高级特性
    JPA 中能够支持面向对象的高级特性,比如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
    下面的这个表格中列出了当前常用持久化技术的优缺点。

表 1 持久化技术的优缺点
支持内容: 序列化  JDBC  ORM  ODB  EJB 2.X  JDO  EJB 3 (JPA)
Java 对象  Yes  No  Yes  Yes  Yes  Yes  Yes
高级 OO 原理  Yes  No Yes  Yes  No  Yes  Yes
事务完整性 No  Yes  Yes  Yes  Yes  Yes  Yes
并发 No  Yes  Yes  Yes  Yes  Yes  Yes
大数据集 No  Yes  Yes  Yes  Yes  Yes  Yes
现有 Schema  No  Yes  Yes  No  Yes  Yes  Yes
关系型和非关系型数据存储 No  No  No  No  Yes  Yes  No
查询 No  Yes  Yes  Yes  Yes  Yes  Yes
严格的标准 / 可移植  Yes  No  No  No  Yes  Yes  Yes
简单易用 Yes  Yes  Yes Yes  No  Yes  Yes

 
 

  • OpenJPA 简介

OpenJPA 是 Apache 组织提供的开源项目,它实现了 EJB 3.0 中的 JPA 标准,为开发者提供功能强大、使用简单的持久化数据管理框架。OpenJPA 封装了和关系型数据库交互的操作,让开发者把注意力集中在编写业务逻辑上。OpenJPA 可以作为独立的持久层框架发挥作用,也可以轻松的与其它 Java EE 应用框架或者符合 EJB 3.0 标准的容器集成。
除了对 JPA 标准的支持之外,OpenJPA 还提供了非常多的特性和工具支持让企业应用开发变得更加简单,减少开发者的工作量,包括允许数据远程传输/离线处理、数据库/对象视图统一工具、使用缓存(Cache)提升企业应用效率等。

  1. 数据远程传输 / 离线处理
    JPA 标准规定的运行环境是 "本地" 和 "在线" 的。本地是指 JPA 应用中的 EntityManager 必须直接连接到指定的数据库,而且必须和使用它的代码在同一个 JVM 中。在线是指所有针对实体的操作必须在一个 EntityManager 范围中运行。这两个特征,加上 EntityManager 是非序列化的,无法在网络上传输,导致 JPA 应用无法适用于企业应用中的 C/S 实现模式。OpenJPA 扩展了这部分接口,支持数据的远程传输和离线处理。
  2. 数据库 / 对象视图统一工具
    使用 OpenJPA 开发企业应用时,保持数据库和对象视图的一致性是非常重要的工作,OpenJPA 支持三种模式处理数据库和对象视图的一致性:正向映射(Forward Mapping)、反向映射(Reverse Mapping)、中间匹配(Meet-in-the-Middle Mapping),并且为它们提供了相应的工具支持。
    ? 正向映射 是指使用 OpenJPA 框架中提供的 org.apache.openjpa.jdbc.meta.MappingTool 工具从开发者提供的实体以及在实体中提供的对象 / 关系映射注释生成相应的数据库表。
    ? 反向映射 是指 OpenJPA 框架中提供的 org.apache.openjpa.jdbc.meta.ReverseMappingTool 工具从数据库表生成符合 JPA 标准要求的实体以及相应的对象 / 关系映射注释内容。
    ? 中间匹配 是指开发者负责创建数据库表、符合 JPA 标准的实体和相应的对象 / 关系映射注释内容,使用 OpenJPA 框架中提供的 org.apache.openjpa.jdbc.meta.MappingTool 工具校验二者的一致性。
  3. 使用缓存提升效率
    性能是企业应用重点关注的内容之一,缓存是提升企业系统性能的重要手段之一。OpenJPA 针对数据持久化提供多种层次、多方面的缓存支持,包括数据、查询、汇编查询的缓存等。这些缓存的应用可以大幅度的提高企业应用的运行效率。

 第 2 部分: 开发第一个 Open JPA 应用

 

  •  下载及安装 OpenJPA
  1. 支持环境准备

由于 OpenJPA 是基于注释机制的框架,这需要用到 JDK 5.0 或者以上版本,因此请确保在工作机器上已经下载和安装了 JDK 5.0(参见 参考资源)。

演示过程中,我们需要一个数据库作为对象持久化的目标数据库。出于简单和方便的考虑,我们选择采用 MySQL 数据库,因此您需要下载 MySQL 数据库安装包(请见 参考资源)。如果需要 MySQL 很好的支持中文字符(GB2312 编码),您可选择下载 MySQL 5.0 或者以上版本,安装的时候选择数据库字符集为 GB2312 即可。

 

    2. 下载、安装 OpenJPA

OpenJPA 的最新稳定版本是 Open JPA 0.97(下载链接见 参考资源)。OpenJPA 的安装比较简单,只需要将下载的压缩文件解压即可。我们假设将下载的压缩包解压到 C:/OpenJPA 目录下(本文后面将使用 %OPENJPA_HOME% 来引用这个目录)。

 

  • 使用 OpenJPA 开发 EJB 3.0 应用
  1. 编写(或修改)OpenJPA 配置文件;
    在非 JavaEE 环境下,OpenJPA 通常使用 CLASSPATH 环境下的 META-INF\persistence.xml 文件来创建 EntityManagerFactory,而每一个被管理的 Java 实体类必须在 persistence.xml 中注册后才能被 EneityManager 处理。
  2. 根据业务需要设计 Java 对象、编写对应的 Java 实体类;
  3. 用 JDK 编译 Java 实体类;
  4. 用 OpenJPA 提供的工具 — PCEnhancer 来增强(enhance)编译好的 Java 实体类;被增强过的类可以提供更好的运行性能、灵活的 "懒加载" 等方面的优势,更多详细的内容请参考 OpenJPA 的帮助文档。
  5. 使用 OpenJPA 提供的工具 MappingTool 从 Java 对象生成数据库定义文件(DDL);可以通过 MappingTool 工具直接保持 Entity 和数据库之间的一致性,也可以使用 MappingTool 工具生成的数据库定义文件(DDL)创建应用正常运行所需要的数据库结构。
  6. 将创建的实体类注册到 OpenJPA 容器中;
  7. 应用 JPA 接口编写实体类访问代码,实现相应的业务逻辑。
  8. 下面我们将用一个简单的例子来说明创建 OpenJPA 应用的典型步骤,实例中我们将创建名为 Animal 的持久化对象,它有两个属性,分别是 “id”和 “name”,Animal 对象将被持久化到本地的 MySQL 数据库中,其中 id 属性对应的数据库字段由 MySQL 数据库自动生成。
  • 开发环境说明

 

本文的演示说明均基于 Windows XP 平台,JDK 版本为 1.5.0_11,数据库服务器为 MySQL 5.0,和演示代码位于同一台机器上。所有演示用例对应的 MySQL 数据库为“openjpa”,访问 MySQL 的用户名和密码也均为“openjpa”。

 

  • OpenJPA 应用开发典型步骤 
  1. 建立工程目录

在 C: 盘根目下创建名为 OpenJPAExamples 的目录,我们所有的类文件和配置文件都将放在这个目录下。

 

    2. 编写 JPA 配置文件

在 C:\OpenJPAExamples 新建 META-INF 目录,随后在该目录下创建 persistence.xml 文件。persistence.xml 是 OpenJPA 的配置文件,提供 OpenJPA 容器初始化、运行所需要的配置信息。比如 OpenJPA 的事务策略、数据库的连接信息等。清单 1 中是我们演示实例中所使用的 persistence.xml 文件的内容。

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
	<!-- persistence-unit的name属性提供了创建EntityManagerFacotry时的
		关键字,transaction-type则指定了使用的事务管理类型,这里使
		用‘RESOURCE_LOCAL’参数表示使用本地事务 -->
	<persistence-unit name="mysql" transaction-type="RESOURCE_LOCAL">
		<!-- JPA的提供类,OpenJPA的设置如下,如果使用其它的JPA实现,这里
			的内容需要修改成相应的提供类
			<provider>
			org.apache.openjpa.persistence.PersistenceProviderImpl
			</provider>
		-->
		<!-- OpenJPA容器中管理的实体类列表 -->
		<class>org.vivianj.openjpa.entity.Animal</class>
		<!--  OpenJPA容器访问数据库的参数 -->
		<properties>
			<property name="openjpa.ConnectionURL"
				value="jdbc:mysql://localhost/openjpa" />
			<property name="openjpa.ConnectionDriverName"
				value="com.mysql.jdbc.Driver" />
			<property name="openjpa.ConnectionUserName" value="openjpa" />
			<property name="openjpa.ConnectionPassword" value="openjpa" />
		</properties>
	</persistence-unit>
</persistence>

 

 

   3. 创建实体类

新创建实体类 Animal,为了说明的简单,该类只有两个属性:id 和 name,其中 id 字段代表的是编号(编号由 MySQL 数据库自动生成),name 属性表示名称。实体类的全部代码见清单 2,请注意其中的黑体部分,它们是 JPA 中定义的注释(Annotation),Animal 中仅仅用到了非常少的几个注释,了解其它更多注释的细节请参看 OpenJPA 的帮助文档。

package org.vivianj.openjpa.entity;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/* 
 *  使用Entity注释表示该类是实体类,它的name属性是该实体在查询中对应的
 *  唯一名称,如果没有提供Entity的name属性,默认的name属性是类名。
 */
@Entity
public class Animal {
    // 编号
    /* 使用Id注释表示该字段是标识字段 */
    /*
     * 使用GeneratedValue注释定义该标识字段的产生方式,我们的演示系统中
     * id由MySQL数据库字段自动生成,因此选择GenerationType.IDENTITY,
     * 另外的可选方式包括GeneratorType.AUTO,GenerationType.SEQUENCE,
     * GenerationType.TABLE。
     */
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    // 名称
    /*
     * Basic注释表示该属性是持久化属性,没有使用Basic注释的属性将不会被持久化到数据库中
     */
    @Basic
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  

 

    4. 编译实体类

 

    5. 增强(Enhance)实体类

 

 

Enhance 是使用 OpenJPA 必须的一个步骤,所谓 Enhance 是指使用 OpenJPA 提供的工具 PCEnhancer(org.apache.openjpa.enhance.PCEnhancer)对实体类进行处理的过程,被 Enhance 过的实体类能够支持性能优化、懒惰式装载等高级特性。

 

 OpenJPA 支持在编译时、部署时、运行时增强实体类,我们以编译时为例了解如何增强实体类。我们使用下面的系列语句完成实体类的增强(Enhance)。

 

1.	C:\OpenJPAExamples>set OPENJPA_HOME=C:\OpenJPA
2.	C:\OpenJPAExamples>set classpath= %OPENJPA_HOME%\lib\commons-collections-3.2.jar;
	%OPENJPA_HOME%\lib\commons-lang-2.1.jar;
	%OPENJPA_HOME%\lib\commons-logging-1.0.4.jar;
	%OPENJPA_HOME%\lib\commons-pool-1.3.jar;
	%OPENJPA_HOME%\lib\geronimo-j2ee-connector_1.5_spec-1.0.1.jar;
	%OPENJPA_HOME%\lib\geronimo-jms_1.1_spec-1.0.1.jar;
	%OPENJPA_HOME%\lib\geronimo-jta_1.0.1B_spec-1.0.1.jar;
	%OPENJPA_HOME%\lib\persistence-api-1.0.jar;
	%OPENJPA_HOME%\lib\serp-1.11.0.jar;
	%OPENJPA_HOME%\openjpa-all-0.9.6-incubating.jar
3.	C:\OpenJPAExamples>java org.apache.openjpa.enhance.PCEnhancer Animal.java

 

从中我们可以看到 Animal 被增加了新的 org.apache.openjpa.enhance.PersistenceCapable 接口,而该接口声明的方法都是和实体创建、状态保持等相关的。事实上,OpenJPA 通过 Enhance 过程修改我们的实体类,扩充了实体类的能力,从而实现性能优化、懒惰式装载等高级特性。

1.	public class org.vivianj.openjpa.entity.Animal 
2.	    extends java.lang.Object 
3.	    implements org.apache.openjpa.enhance.PersistenceCapable{
4.	    protected transient org.apache.openjpa.enhance.StateManager pcStateManager;
5.	    protected transient byte pcFlags;
6.	    static java.lang.Class class$Ljava$lang$String;
7.	    static java.lang.Class class$Lorg$vivianj$openjpa$entity$Animal;
8.	    public org.vivianj.openjpa.entity.Animal();
9.	    public int getId();
10.	    public void setId(int);
11.	    public java.lang.String getName();
12.	    public void setName(java.lang.String);
13.	    static final {};
14.	    static java.lang.Class class$(java.lang.String);
15.	    protected void pcClearFields();
16.	    public org.apache.openjpa.enhance.PersistenceCapable pcNewInstance(
17.		org.apache.openjpa.enhance.StateManager, java.lang.Object, boolean);
18.	    public org.apache.openjpa.enhance.PersistenceCapable pcNewInstance(
19.		org.apache.openjpa.enhance.StateManager, boolean);
20.	    protected static int pcGetManagedFieldCount();
21.	    public void pcReplaceField(int);
22.	    public void pcReplaceFields(int[]);
23.	    public void pcProvideField(int);
24.	    public void pcProvideFields(int[]);
25.	    protected void pcCopyField(org.vivianj.openjpa.entity.Animal, int);
26.	    public void pcCopyFields(java.lang.Object, int[]);
27.	    public java.lang.Object pcGetGenericContext();
28.	    public java.lang.Object pcFetchObjectId();
29.	    public boolean pcIsDeleted();
30.	    public boolean pcIsDirty();
31.	    public boolean pcIsNew();
32.	    public boolean pcIsPersistent();
33.	    public boolean pcIsTransactional();
34.	    public boolean pcSerializing();
35.	    public void pcDirty(java.lang.String);
36.	    public org.apache.openjpa.enhance.StateManager pcGetStateManager();
37.	    public java.lang.Object pcGetVersion();
38.	    public void pcReplaceFlags();
39.	    public synchronized void pcReplaceStateManager(
40.	          org.apache.openjpa.enhance.StateManager)       
41.		          throws java.lang.SecurityException;
42.	    public void pcCopyKeyFieldsToObjectId(
43.	        org.apache.openjpa.enhance.FieldSupplier, java.lang.Object);
44.	    public void pcCopyKeyFieldsToObjectId(java.lang.Object);
45.	    public void pcCopyKeyFieldsFromObjectId(
46.	         org.apache.openjpa.enhance.FieldConsumer, java.lang.Object);
47.	    public void pcCopyKeyFieldsFromObjectId(java.lang.Object);
48.	    public java.lang.Object pcNewObjectIdInstance(java.lang.Object);
49.	    public java.lang.Object pcNewObjectIdInstance();
50.	    public java.lang.Boolean pcIsDetached();
51.	    public java.lang.Object pcGetDetachedState();
52.	    public void pcSetDetachedState(java.lang.Object);
53.	}

 

 

 6. 将新创建的实体类注册到 OpenJPA 容器中

 

1.	<persistence …>
2.	    <persistence-unit …>
3.	        …
4.	        <class>org.vivianj.openjpa.entity.Animal</class>
5.	        …
6.	    </persistence-unit>
7.	</persistence>

 

7. 准备数据库

 

8. 保持实体类和数据库表结构一致性

 

OpenJPA 中提供了专门的 MappingTool(org.apache.openjpa.jdbc.meta.MappingTool)工具协助开发者保持实体类和数据库表结构之间的一致性。MappingTool 工具能够自动的根据实体类中提供的注释(Annotation),识别出对象、对象之间的继承/包含等关系以及如何在关系型数据库中处理这些关系的设置,自动保证实体类和数据库之间的一致性,开发者也可以选择使用 OpenJPA 生成创建数据库所需要的 SQL 语句,然后手动的保持实体类和数据库之间的一致性。

可以使用下面的命令语句直接通过 MappingTool 在数据库中创建 OpenJPA 应用所需要的数据库表。

 

java org.apache.openjpa.jdbc.meta.MappingTool Animal.java

 

命令执行完成后,我们通过 MySQL 的客户端访问 OpenJPA 数据库,可以发现里面已经创建了名为“animal”的数据表。如果开发者不想让 MappingTool 自动维护 OpenJPA 和数据库之间的映射,而只是想让 MappingTool 协助生成创建数据库所需要的数据库表定义文件(DDL),可以使用下面的命令语句,其中的参数 -sql 的值就是生成数据库表定义文件的位置。

比如要将生成的数据库表定义文件写入当前文件夹下 animal.sql 文件的命令如下:

 

java org.apache.openjpa.jdbc.meta.MappingTool –sql animal.sql Animal.java

 

[注] 要成功执行 MapptingTool 工具,必须先将数据库的 JDBC 驱动放入到 classpath 中,否则会得到一个 org.apache.openjpa.util.StoreException 类型的异常信息。

 

  •  访问持久化对象

 

访问 OpenJPA 容器中管理的持久化对象所需要的基本步骤如下:

  1. 获取 OpenJPA 容器中配置好的 EntityManagerFactory 对象;
  2. 从 EntityManagerFactory 中获取 EntityManager 对象;
  3. 如果是处理持久化对象的创建、更新、删除动作,还需要从 EntityManager 中获取 EntityTransaction,并且调用其 begin(commit)方法显式的启动(提交)事务;
  4. 操作持久化对象
    调用 EntityManager 的 persist 方法可以持久化对象到数据库中;
    调用 EntityManager 的 merge 方法可以更新对象状态;
    调用 EntityManager 的 remove 方法可以删除持久化对象,另外一种选择是通过 Query 接口删除对象。
  5. 查询持久化对象
    根据对象的主键查找符合条件的对象,可以直接使用 EntityManager 的 find 方法;
    要查询 EntityManager 中符合条件的对象列表,还需要借助 Query 接口和 JPQL(Java Persistence Query Language)查询语言。

获取 OpenJPA 容器中的 EntityManagerFactory

EntityManagerFactory 是 OpenJPA 中创建 EntityManager 的工厂,要想得到 EntityManager,就必须获取的相应的 EntityManagerFactory。

EntityManagerFactory 通过 Persistence 的静态方法 createEntityManagerFactory 获得,该方法是一个重载的方法,支持不同的输入参数。最常用的是使用一个字符串作为参数,该字符串的内容是 EntityManagerFactory 的标识名,该标识名需要和 persistence.xml 文件中的 persistence-unit 元素的 name 属性保持一致。可以使用 null 作为 createEntityManagerFactory 方法的参数,这时候将使用 persistence.xml 中没有提供 name 属性的 persistence-unit 元素提供的参数来配置 EntityManagerFactory。

下面的代码段可以从 OpenJPA 容器中获取名为“mysql”的 EntityManagerFactory。

EntityManagerFactory factory = Persistence.createEntityManagerFactory("mysql");


而它对应的 persistence.xml 文件中,应该有相应的 name 属性为“mysql”的 persistence-unit 元素,下面的配置是一个示例。

1. <persistence xmlns="http://java.sun.com/xml/ns/persistence"
2.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3.     version="1.0">
4.     <persistence-unit name="mysql" transaction-type="RESOURCE_LOCAL">
5.  …
6.     </persistence-unit>
7. </persistence>
 
为了提升软件的执行效率,我们通常选择在某一个范围内缓存 EntityManagerFactory 对象。在 EntityManagerFactory 使用完后,我们需要调用它的 close 方法来释放相应的资源。

 

获取 EntityManager

要访问 OpenJPA 容器中的实体类,必须首先获得相应的 EntityManager。可以通过 EntityManagerFactory 对象的 createEntityManager() 方法来获取 EntityManager 对象。

 

EntityTransaction的启动(提交)

对于 OpenJPA 容器中的持久化对象的创建、修改、删除操作必须在代码中显式的处理事务,而对于查询操作则不需要在代码中显式的处理事务。JPA 应用中的事务由 EntityTransaction 接口处理,EntityTransaction 可以直接通过 EntityManager 对象的 getTransaction 方法获得。我们可以调用 EntityTransaction 的 begin(commit) 方法显式的启动(提交)事务。获取、使用 EntityTransaction 的实际例子请参考 清单 4 AnimalDAOImpl.java 中的源代码。

 

Query 接口和 JPQL 查询语言

要查询 EntityManager 中符合条件的对象列表,需要借助 Query 接口和 JPQL。Query 接口可以直接通过 EntityManager 的 createQuery 方法获得。Query 对象目前支持 JPQL 和原生态 SQL 两种方式。

JPQL 是 OpenJPA 中支持的对象查询语言,是 EJB SQL 的一种实现。通过 JPQL,我们可以用一种面向对象的方式编写持久化对象的查询条件。比如要查找编号为“1”的 Animal 对象,我们可以使用下面的 JPQL 语法:

select animal form Animal animal where animal.id=1)
关于 JPQL 的更多信息请参考 OpenJPA 的帮助文档。 

 

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.vivianj.openjpa.entity.Animal;


public class AnimalDAOImpl {
    /**
     * removeAnimal方法可以从数据库中删除指定编号的Animal对象
     * 
     * @param id
     *            Animal对象的编号
     */
    public void removeAnimal(int id) {
        // 获取EntityManagerFactory
        EntityManagerFactory factory = Persistence
                .createEntityManagerFactory("mysql");
        // 获取EntityManager
        EntityManager em = factory.createEntityManager();
        // 开始事务处理
        em.getTransaction().begin();

        // 使用Query删除对象
        em.createQuery("delete from Animal animal where animal.id=" + id)
                .executeUpdate();

        // 我们还可以选择通过Query对象来完成
        /*
         * // 从EntityManager中查询到符合条件的对象 Animal animal =
         * em.find(Animal.class,id); // 调用EntityManager的remove方法删除对象
         * em.remove(animal);
         */

        // 提交事务
        em.getTransaction().commit();
        // 关闭EntityManager
        em.close();
        // 关闭EntityManagerFactory
        factory.close();

    }

    /**
     * findAnimalsByName 通过输入的name内容模糊查找符合条件的Animal对象列表
     * 
     * @param name
     *            Animal对象的name
     * @return 符合模糊查找条件的Animal对象列表
     */
    public List<Animal> findAnimalsByName(String name) {
        // 获取EntityManagerFactory
        EntityManagerFactory factory = Persistence
                .createEntityManagerFactory("mysql");
        // 获取EntityManager
        EntityManager em = factory.createEntityManager();

        /*
         * 通过EntityManager的createQuery方法获取Query对象
         * createQuery方法的参数是JPQL查询语句,JPQL语句的语法请参考OpenJPA的帮助文档.
         * 
         * 由于查询不需要事务的支持,因此Query操作的前后没有出现begin、commit方法的调用
         * 
         */
        Query q = em
            .createQuery("select animal from Animal animal where animal.name like '%" + name + "%' ESCAPE ''");
        List<Animal> lstRet = q.getResultList();
        // 关闭EntityManager
        em.close();
        // 关闭EntityManagerFactory
        factory.close();

        return lstRet;
    }

    /**
     * getAnimalByPrimaryKey 方法可以查找符合条件的单个Animal对象, 如果不存在对应的Animal对象将返回null
     * 
     * @param id
     *            Animal对象的编号
     * @return 唯一符合条件的Animal对象
     * 
     */
    public Animal getAnimalByPrimaryKey(int id) {
        // 获取EntityManagerFactory
        EntityManagerFactory factory = Persistence
                .createEntityManagerFactory("mysql");
        // 获取EntityManager
            EntityManager em = factory.createEntityManager();
    
            // 查找符合条件的对象
            Animal animal = em.find(Animal.class,id);
    
            // 关闭EntityManager
            em.close();
            // 关闭EntityManagerFactory
            factory.close();
    
            return animal;
        }
    
        /**
         * 将对象持久化到数据库中
         * 
         * @param animal
         *            需要被持久化的对象
         */
        public void persistAnimal(Animal animal) {
            // 获取EntityManagerFactory
            EntityManagerFactory factory = Persistence
                    .createEntityManagerFactory("mysql");
            // 获取EntityManager
            EntityManager em = factory.createEntityManager();
            // 开始事务处理
            em.getTransaction().begin();
    
            // 持久化对象
            em.persist(animal);
    
            // 提交事务
            em.getTransaction().commit();
            // 关闭EntityManager
            em.close();
            // 关闭EntityManagerFactory
            factory.close();
        }
    
        /**
         * 将Animal对象被更新的属性持久化到数据库中
         * 
         * @param animal
         *            被更新过的Animal对象
         */
        public void updateAnimal(Animal animal) {
            // 获取EntityManagerFactory
            EntityManagerFactory factory = Persistence
                    .createEntityManagerFactory("mysql");
            // 获取EntityManager
            EntityManager em = factory.createEntityManager();
            // 开始事务处理
            em.getTransaction().begin();
    
            // 更新持久化对象状态
            em.merge(animal);
    
            // 提交事务
            em.getTransaction().commit();
            // 关闭EntityManager
            em.close();
            // 关闭EntityManagerFactory
            factory.close();
        }
}

 

 

 第 3 部分: 实体继承

 

1. 类及其子类保存在一张数据库表中

 Animal、Fish、Dog 三个类的所有对象实例都被保存在 Animal 数据表中,该表将会有 5 个属性,其中 ID,NAME 字段对应 ANIMAL 类的两个属性,ID、NAME、SEX 对应 Dog 类的属性,ID、NAME、STERRITORY 对应 Fish 类的属性。DTYPE 是 OpenJPA 加入的字段,用于确定当前记录的实际类类型,在这里例子中,它的内容是“ANIMAL”、“FISH”或者是“DOG”。

 

2. 类和子类分别保存在不同的数据库表中,互相之间没有关联

 

3. 类和子类分别保存在不同的数据库表中,子类中不保存父类中已有的属性,仅通过主键进行关联

 

这种情况下,父类和子类对应不同的表,但是子类对应的表中不再保存父类对应表中已经存在的字段信息,两个表之间通过关键字段关联起来,也就是数据库技术中通常所说的外健。这种实现方式是最理想化的一种,既能够处理对象之间的继承,又满足了关系数据库中对于设计范式的要求。

 

这三种方式的处理对于开发者而言是透明的,无论选择哪一种,仅仅影响数据在关系数据库中的保存方式,对于开发者而言,只需要按照面向对象的方式操作对象既可,OpenJPA 框架在处理持久化操作的时候,会动态地判断当前对象的实际类类型(后期绑定),从而确定持久化到哪个表中。在一个企业应用的实现中,开发者可以根据需要选择这三种方式的一种或者几种来处理对象之间的继承关系。

 

OpenJPA 是一个基于注释的持久化框架,对持久化的大多数元信息都只需要为实体类提供相应的注释。开发者使用注释描述实体和数据库表之间的映射,也采用注释描述对象继承关系的持久化。javax.persistence.Inheritance 注释用来指定对象继承关系持久化的方式。它的 strategy 属性用于指定持久化对象继承关系在关系数据库中的表现形式,可选择项包括 SINGLE_TABLE、JOINED 和 TABLE_PER_CLASS。它们三个都是 javax.persistence.InheritanceType 中定义的常量。

  • SINGLE_TABLE

    strategy 设置为 SINGLE_TABLE 选项表示所有类及其子类保存在同一个数据库表中,对象的类型使用表中的特殊字段 DTYPE 进行识别。

  • TABLE_PER_CLASS

    strategy 设置为该选项表示每个类使用一个表。

  • JOINED

    strategy 设置为该选项表示父类和子类分别保存在不同的数据库表中,子类中不保存父类对应数据库表中已有的属性,仅通过主键进行关联。

javax.persistence.Inheritance 注释是类级别的注释。需要为每一个成为父类的实体类提供 javax.persistence.Inheritance 注释并且指定 strategy 属性。在同一个企业应用中,开发者可以根据实际情况选择这三种策略中的一种,或者是几种同时使用。

 

Animal.java, 继承关系的父类

1.	package chapter04.entity;

2.	import javax.persistence.Entity;
3.	import javax.persistence.Id;
4.	import javax.persistence.Inheritance;
5.	import javax.persistence.InheritanceType;
6.	
7.	@Entity
8.	@Inheritance(strategy = InheritanceType.JOINED)
9.	public class Animal {
10.		@Id
11.		private int id;
12.	
13.		private String name;
14.	
15.		public Animal() {
16.		}
17.	
18.		public Animal(int id, String name) {
19.			this.id = id;
20.			this.name = name;
21.		}
22.	
23.		public int getId() {
24.			return id;
25.		}
26.	
27.		public void setId(int id) {
28.			this.id = id;
29.		}
30.	
31.		public String getName() {
32.			return name;
33.		}
34.	
35.		public void setName(String name) {
36.			this.name = name;
37.		}
38.	
39.	}

 

Fish.java, 继承关系中的子类

1.	package chapter04.entity;
2.	
3.	import javax.persistence.Entity;
4.	
5.	@Entity
6.	public class Fish extends Animal {
7.		/* 鱼的活动范围,比如江、河、湖、海 */
8.		private String territory;
9.	
10.		public Fish() {
11.		}
12.	
13.		public Fish(int id, String name, String territory) {
14.			super(id, name);
15.			this.territory = territory;
16.		}
17.	
18.		public String getTerritory() {
19.			return territory;
20.		}
21.	
22.		public void setTerritory(String territory) {
23.			this.territory = territory;
24.		}
25.	
26.	}

 

Dog.java, 继承关系中的子类

1.	package chapter04.entity;
2.	
3.	import javax.persistence.Entity;
4.	
5.	@Entity
6.	public class Dog extends Animal {
7.		/* 性别 */
8.		private String sex;
9.	
10.		public Dog() {
11.		}
12.	
13.		public Dog(int id, String name, String sex) {
14.			super(id, name);
15.			this.sex = sex;
16.		}
17.	
18.		public String getSex() {
19.			return sex;
20.		}
21.	
22.		public void setSex(String sex) {
23.			this.sex = sex;
24.		}
25.	
26.	}

 

 

持久化 Animal

1.	// 通过 Persistence 创建 EntityManagerFactory
2.	EntityManagerFactory factory = Persistence.createEntityManagerFactory(
3.			"jpa-unit", System.getProperties());
4.	
5.	// 从 EntityManagerFactory 中创建 EntityManager
6.	EntityManager em = factory.createEntityManager();
7.	
8.	// 开始持久化实体的事务
9.	em.getTransaction().begin();
10.	
11.	// 使用相同的方式持久化实体
12.	em.persist(new Animal(1,"honey"));
13.	
14.	// 提交持久化实体的事务
15.	em.getTransaction().commit();
16.	
17.	// 关闭EntityManager
18.	em.close();

 

当我们执行这段代码时,OpenJPA 会将它转化为关系数据库对应的 SQL 语句:

 

INSERT INTO Animal (id, name) VALUES (1, 'honey')

 

 

持久化 Fish

 

1.	// 通过 Persistence 创建 EntityManagerFactory
2.	EntityManagerFactory factory = Persistence.createEntityManagerFactory(
3.			"jpa-unit", System.getProperties());
4.	
5.	// 从 EntityManagerFactory 中创建 EntityManager
6.	EntityManager em = factory.createEntityManager();
7.	
8.	// 开始持久化实体的事务
9.	em.getTransaction().begin();
10.	
11.	// 使用相同的方式持久化实体
12.	em.persist(new Fish(2,"mermaid","SEA"));
13.	
14.	// 提交持久化实体的事务
15.	em.getTransaction().commit();
16.	
17.	// 关闭EntityManager
18.	em.close();

 

由于 Fish 对象的属性保存在两个表中,因此当我们执行这段代码时,OpenJPA 会将它转化为对应的两条 SQL 语句:

 

INSERT INTO Animal (id, name) VALUES (2, 'mermaid')
INSERT INTO Fish (id, territory) VALUES (2, 'SEA')

 

持久化 Dog

持久化 Dog 对象和持久化 Fish 对象的过程几乎一样,区别是 persist 方法的参数变成了 Dog 对象。

 

em.persist(new Dog(3,"ba guai","MALE"));

 

和持久化 Fish 对象时一样,Dog 对象的属性也保存在两个表中,因此当我们执行这段代码时,OpenJPA 会将它转化为对应的两条 SQL 语句:

 

INSERT INTO Animal (id, name) VALUES (3, 'ba guai')
INSERT INTO Dog (id, sex) VALUES (3, 'MALE')

 

获取所有 Animal 对象

1.	// 通过 Persistence 创建 EntityManagerFactory
2.	EntityManagerFactory factory = Persistence.createEntityManagerFactory(
3.			"jpa-unit", System.getProperties());
4.	// 创建新的 EntityManager
5.	EntityManager em2 = factory.createEntityManager();
6.	
7.	// 查询所有 Animal 对象
8.	Query q = em2.createQuery("select m from Animal m");
9.	
10.	// 直接处理 Animal 对象,打印 Animal 对象的信息
                
11.	for (Animal m : (List<Animal>) q.getResultList()) {
                
12.		System.out.println("Animal Object:");
                
13.		System.out.println("  id:" + m.getId());
                
14.		System.out.println("  name:" + m.getName());
                
15.	}
16.	
17.	// 关闭 EntityManager 和 EntityManagerFactory
18.	em2.close();
19.	factory.close();

 

当我们执行这段代码时,OpenJPA 会将它转化为关系数据库对应的 SQL 查询语句:

SELECT t0.id, t1.id, t2.id, t0.name, t1.sex, t2.territory 
FROM Animal t0 
LEFT OUTER JOIN Dog t1 ON t0.id = t1.id 
LEFT OUTER JOIN Fish t2 ON t0.id = t2.id

 

在查询结果返回后,OpenJPA 会将查询结果影射到相关的 Animal 对象上,整个过程是透明的,开发者只需要处理对象模型即可。

 

获取所有 Fish 对象

1.	// 通过 Persistence 创建 EntityManagerFactory
2.	EntityManagerFactory factory = Persistence.createEntityManagerFactory(
3.			"jpa-unit", System.getProperties());
4.	// 创建新的 EntityManager
5.	EntityManager em2 = factory.createEntityManager();
6.	
7.	// 查询所有 Fish 对象
8.	Query q1 = em2.createQuery("select fish from Fish fish");
9.	
10.	// 打印 Fish 对象的信息
11.	for (Fish fish : (List<Fish>) q1.getResultList()) {
12.		System.out.println("Fish Object:");
13.		System.out.println("  id:" + fish.getId());
14.		System.out.println("  name:" + fish.getName());
15.	    System.out.println("  territory:" + fish.getTerritory());
16.	}
17.	
18.	// 关闭 EntityManager 和 EntityManagerFactory
19.	em2.close();
20.	factory.close();

 

当我们执行这段代码时,OpenJPA 会将它转化为关系数据库对应的 SQL 查询语句:

SELECT t1.id, t0.id, t1.name, t0.territory 
FROM Fish t0 
INNER JOIN Animal t1 ON t0.id = t1.id

 

获取所有 Dog 对象

获取 Dog 对象的过程和获取 Fish 对象的过程一致,开发者只需要将 Query 接口使用的 JPQL 语句改为“select dog from Dog dog”。

 

Query q1 = em2.createQuery("select dog from Dog dog ");

 

当我们执行这段代码时,OpenJPA 会将它转化为关系数据库对应的 SQL 查询语句:

 

SELECT t1.id, t0.id, t1.name, t0.sex 
FROM Dog t0 
INNER JOIN Animal t1 ON t0.id = t1.id

 

在查询结果返回后,OpenJPA 会将查询结果影射到相关的Fish对象上,整个过程是透明的,开发者只需要处理对象模型即可。

对象继承关系在关系数据库中的表现是对象持久化中难于实现的部分,OpenJPA 为开发者提供了一种透明的实现。在 OpenJPA 中提供了 SINGLE_TABLE、JOINED 和 TABLE_PER_CLASS 三种实现方式处理实体继承关系,开发者需要做的仅仅是为实体类提供 javax.persistence.Inheritance 注释,同时设置它的 strategy 属性,确定使用哪种对象继承关系即可,和关系数据库交互的部分由 OpenJPA 框架完成。

 

你可能感兴趣的:(apache,应用服务器,ejb,网络应用,企业应用)