【IT168 技术文章】
融入 Java EE
OpenJPA 是标准的 JPA 框架,因此它能够被任何的 EJB3.0 容器所集成,作为 JPA 的一种实现。比如我们可以将 JBoss 应用服务器中的 JPA 实现框架由系统默认的 Hibernate 切换成 OpenJPA,同样,我们可以将 Websphere、WebLogic 等的 JPA 实现框架由系统默认的框架切换成 OpenJPA。
要将 OpenJPA 容器 Java EE 容器,和 OpenJPA 作为独立框架运行时相比需要完成几部分的转换:
部署形式变为 EJB Jar 或者 EAR;OpenJPA 应用在 Java EE 环境下部署形式是 EJB Jar 或者 EAR,而不再是独立的 Java 应用或者 Servlet 容器中的 Web 应用。
使用系统级 JDBC 数据源,将事务处理委托给 JTA 事务;
在前面几篇文章中,我们开发 OpenJPA 应用时都是在 OpenJPA 配置文件 persistence.xml 文件中直接提供访问数据库的 JDBC 配置,操作实体的时候,我们也需要处理使用 entityManager.getTransaction().begin()、entityManager.getTransaction().commit() 这样的语句显示的处理事务。
在 Java EE 环境下,JDBC 通常都是由容器管理,JDBC 访问时的事务也通常使用容器管理,这样可以获得更大的灵活性,也能够最大化的利用容器的特性让企业应用更加强壮。要将 OpenJPA 容器 Java EE 容器,我们首要的任务就是将 OpenJPA 中的应用级 JDBC 数据源切换到 Java EE 容器的 JDBC 数据源上,另外还需要将事务处理委托给 Java EE 容器提供的 JTA 事务,而不在使用 entityManager.getTransaction().begin()、entityManager.getTransaction().commit() 这样的语句显示的处理事务。
注入 Entity Manager Factory 或者 Entity Manager
在前面几篇文章中,我们开发 OpenJPA 应用中操纵实体之前,都需要通过 Persistence 的 createEntityManagerFactory 方法创建 EntityManagerFactory 对象,然后创建 EntityManager 对象后操作实体。
但是根据 EJB3.0 规范中 JPA 部分的要求,在 Java EE 容器中的 JPA 应用应该通过依赖注入获取 Entity Manager Factory 或者是 EntityManager,也可以选择将 Entity Manager Factory 或者是 EntityManager 绑定到 JNDI,在代码中通过 JNDI 获取,而不是采用 Persistence 的 createEntityManagerFactory 方法来创建。
要将 OpenJPA 应用切换到 Java EE 环境下,我们需要向 OpenJPA 中注入 Entity Manager Factory 或者是 EntityManager 对象,或者是将 Entity Manager Factory 或者是 EntityManager 绑定到 JNDI,这取决于 Java EE 容器的支持方式和开发者的爱好。除此之外,OpenJPA 应用中对应部分的代码也需要修改,以适应 Java EE 容器端发生的变化。
使用会话 Bean 封装 EntityBean 的访问
在前面几篇文章中,OpenJPA 应用中生成的实体在客户端直接使用 Java 代码调用,然而在 Java EE 容器中的实体肯定是无法被客户端代码直接访问的,而且,根据 EJB3.0 规范的描述,Java EE 容器中的实体无法和 EJB2.1 中的实体一样绑定到 JNDI,因此我们的选择只能是使用会话 Bean 来封装 EntityBean 的访问。
OpenJPA 应用开发
在上一章中,我们了解了如何将 OpenJPA 应用移植到 Java EE 容器时需要完成的工作内容,本章中,我们将通过一个简单的例子来学习如何在 Java EE 容器中开发、部署一个 OpenJPA 应用。
Java EE 环境下,应用 OpenJPA 框架开发 EJB3.0 应用的主要步骤如下:
创建 EJB 应用目录;
在 Java EE 容器中配置 JDBC 数据源;
编写 ( 修改 ) 配置文件;
Java EE 容器通过 EJB jar 中的 META-INF\persistence.xml 文件来创建 EntityManagerFactory,然后在需要的时候将 EntityManagerFactory 对象或者它创建的 EntityManager 对象注入 OpenJPA 容器中。
根据业务需要设计 Java 对象、编写对应的 Java 实体类;
用 JDK 编译 Java 实体类;
用 OpenJPA 提供的工具—PCEnhancer--enhance 编译好的 Java 实体类;
被 enhance 过的类可以提供更好的运行性能、灵活的”懒加载”等方面的优势,更多详细的内容请参考 OpenJPA 的帮助文档。
使用 OpenJPA 提供的工具 MappingTool 从 Java 对象生成数据库定义文件 (DDL);
可以通过 MappingTool 工具直接保持 Entity 和数据库之间的一致性,也可以使用 MappingTool 工具生成的数据库定义文件 (DDL) 创建应用正常运行所需要的数据库结构。
将创建的实体类注册到 OpenJPA 容器中;
应用会话 Bean 封装对实体类的访问;
客户端通过会话 Bean 的访问,达到访问实体的目标。
我们将使用本系列文章的 第 2 部分:第一个 OpenJPA 应用 中使用过的简单例子,我们将创建名为 Animal 的实体,它有两个属性,分别是 id 和 name,Animal 对象将被持久化到本地的 MySQL 数据库中,其中 id 属性对应的数据库字段由 MySQL 数据库自动生成。
演示开发环境说明
下面的演示步骤说明均基于 Windows XP 平台,JDK 版本为 1.5.0_11,数据库服务器为 MySQL5.0,和演示代码位于同一台机器上。所有演示用例对应的 MySQL 数据库为”openjpa”,访问 MySQL 的用户名和密码也均为”openjpa”。
Java EE 容器使用 JBoss 应用服务器 4.2GA 或者版本,默认安装在 C:\jboss-4.2.0.GA 目录下。
Java EE 环境下 OpenJPA 应用开发典型步骤
请读者注意,后面章节中关于操作的说明均基于 Windows XP 操作系统,如果您使用的开发环境运行在其它类型的操作系统之上,请根据实际情况做出相应的调整。
建立工程目录
在 C: 盘根目下创建名为 ejb3demo 的目录,它包括名为 src 的目录,然后在 src 目录下创建合适的文件和目录。ejb3demo\src 目录是标准的 ejb 目录,我们所有的类文件和配置文件都将放在这个目录下。
创建好的 ejb3demo 目录中应该包括如下目录和文件:
ejb3demo
--src
--META-INF
persistence.xml
配置 JDBC 数据源
在 JBoss 应用服务器中配置 JDBC 数据源比较容易,我们只需要将 MySQL 数据库的 JDBC 驱动 jar 文件拷贝到 %JBOSS_HOME%\server\default\lib 目录下,然后在 %JBOSS_HOME%\server\default\deploy 目录下创建新的 mysql-ds.xml 文件,mysql-ds.xml 文件的内容如下:
2. <datasources>
3. <local-tx-datasource>
4. <!-- JDBC 数据源的 JNDI 名称à
5. <jndi-name>mysqlDS</jndi-name>
6. <!—目标数据库的 JDBC 连接字符串à
7. <connection-url>jdbc:mysql://localhost/openjpa</connection-url>
8. <!—目标数据库的 JDBC 驱动类名à
9. <driver-class>com.mysql.jdbc.Driver</driver-class>
10. <!—目标数据库的用户名à
11. <user-name>openjpa</user-name>
12. <!—目标数据库的密码à
13. <password>openjpa</password>
14.
15. <min-pool-size>5</min-pool-size>
16. <max-pool-size>20</max-pool-size>
17. <idle-timeout-minutes>0</idle-timeout-minutes>
18. </local-tx-datasource>
19.
20. </datasources>
编写 JPA 配置文件
persistence.xml 是 EJB3.0 规范中定义的实体的配置文件,提供 OpenJPA 容器初始化、运行所需要的信息,比如 OpenJPA 的事务策略、数据库的连接信息等,由 Java EE 容器读取后初始化 OpenJPA 应用中需要注入的 EntityManagerFactory 或者 EntityManager。
特别要注意的是,我们必须在 persistence.xml 中为 persistence-unit 元素提供 provider 子元素,它的内容是” org.apache.openjpa.persistence.PersistenceProviderImpl”, 这是 OpenJPA 的 Persistence 的实现。如果我们不提供 provider 子元素,JBoss 服务器将默认使用它内置的 Hibernate 框架作为 JPA 容器。
清单 1 中是我们演示实例中所使用的 persistence.xml 文件的内容。
清单 1. src\META-INF\persistence.xml
2. <persistence xmlns="http://java.sun.com/xml/ns/persistence"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. version="1.0">
5. <!—persistence-unit 的 name 属性提供了创建 EntityManagerFacotry 时的
6. 关键字,transaction-type 则指定了使用的事务管理类型,可以是 JTA
7. 或者 RESOURCE_LOCAL,在 Java EE 环境下默认环境是 JTA,这里使用默认值
8. <persistence-unit name="ejb3">
9. <!—JPA 的提供类,OpenJPA 的设置如下,如果使用其它的 JPA 实现,这里
10. 的内容需要修改成相应的提供类
11. <provider>
12. org.apache.openjpa.persistence.PersistenceProviderImpl
13. </provider>
14. <!—使用 Java EE 容器的 JDBC 数据源,JBoss 下 JDBC 数据源以”java:/”开头,
15. mysqlDS 是 JDBC 数据源的 JNDI 名称
16. <jta-data-source>java:/mysqlDS</jta-data-source>
17. <!—OpenJPA 容器中管理的实体类列表
18. <class> org.vivianj.jpademo.beans.Animal </class>
19. </persistence-unit>
20. </persistence>
接下来,我们需要根据业务需要设计 Java 对象、编写 Java 实体类、编译实体类、enhance 编译好的实体类、生成数据库定义文件、将创建的实体类注册到 OpenJPA 容器,这些步骤和在非 Java EE 环境下开发 OpenJPA 应用是一致的,请读者参考本系列文章的 第 2 部分:第一个 OpenJPA 应用 的步骤完成,这里不再赘述。
应用 SessinBean 封装对实体的访问
根据 EJB3.0 规范中对容器的要求来看,实体不能和 EJB2.X 规范中的实体一样可以绑定到 JNDI,因此 EJB 容器外的 Java 代码无法直接访问实体,可选的方法就是使用会话 Bean 封装实体的操作,Java 客户端通过 JNDI 访问会话 Bean,从而达到操作实体的目标。
在 EJB3.0 标准下,开发一个会话 Bean 非常简单,我们只需要定义业务接口类,然后为该业务接口提供 @ javax.ejb.Remote、@javax.ejb.Local 这样的注释,表明该会话 Bean 提供 Remote 接口或者 Local 接口,也可以为一个业务接口同时提供 @ javax.ejb.Remote、@javax.ejb.Local 注释,表示该接口同时支持 Remote、Local 接口。另外还需要提供会话 Bean 的实现类,它需要实现 Remote 接口、Local 接口其中一种或者同时实现两种接口。
演示例子中我们为定义了 Remote 接口 IAnimalDAO、Local 接口 ILocalAnimalDAO,它们都提供可以持久化 Animal 对象的 persistAnimal 方法。然后创建接口的实现类 AnimalDAO,它实现了 IAnimalDAO 和 ILocalAnimalDAO 两个接口,也就是说,这个会话 Bean 能够同时支持 Remote、Local 访问。
IAnimalDAO 接口的全部源代码如下
2.
3. import javax.ejb.Local;
4.
5. import org.vivianj.jpademo.beans.Animal;
6.
7. @Local
8. public interface ILocalAnimalDAO {
9. public void persistAnimal(Animal animal);
10. }
ILocalAnimal 接口类的全部源代码如下:
2.
3. import javax.ejb.Local;
4.
5. import org.vivianj.jpademo.beans.Animal;
6.
7. @Local
8. public interface ILocalAnimalDAO {
9. public void persistAnimal(Animal animal);
10. }
AnimalDAO 实现类中实现了上面定义的两个接口 IAnimalDAO、ILocalAnimalDAO,并且提供了基于 OpenJPA、Java EE 容器的实体访问实现代码。实例中我们选择 @ javax.persistence.PersistenceContext 注释向会话 Bean 中注入 EntityManager 对象,根据实际需求的不同,还可以选择使用 @PersistenceUnit 注释向会话 Bean 中注入 EntityManagerFactory 对象。
AnimalDAO 中还使用了 javax.ejb.Stateless 注释,它用于声明当前的会话 Bean 是一个无状态的会话 Bean。
2.
3. import javax.ejb.Stateless;
4. import javax.persistence.EntityManager;
5. import javax.persistence.PersistenceContext;
6.
7. import org.vivianj.jpademo.beans.Animal;
8.
9. @Stateless
10. public class AnimalDAO implements IAnimalDAO,ILocalAnimalDAO {
11. // 由 Java EE 容器向会话 Bean 中注入 EntityManager 对象
12. @PersistenceContext
13. EntityManager em;
14.
15. public void persistAnimal(Animal animal) {
16. /* 由于 EntityManager 的事务已经委托给 Java EE 容器的 JTA 事务,因此
17. * 这里直接持久化实体 , 不再需要使用 begin()、commit() 显式的处理实体
18. * 操作时候的事务
19. */
20. em.persist(animal);
21. }
22.
23. }
打包、部署 OpenJPA 应用
在 JBoss 中加入 OpenJPA 的 jar 文件
要在 JBoss 应用服务器上运行 OpenJPA 应用,需要将 OpenJPA 的 jar 文件加入到 JBoss 的 CLASSPATH 中,我们需要拷贝 %OPENJPA_HOME%\openjpa-*.jar 文件到 %JBOSS_HOME%\server\default\lib,另外还需要拷贝 %OPENJPA_HOME%\lib 下 common-*.jar 文件和 serp-*.jar 文件到 %JBOSS_HOME%\server\default\lib 下。
[注]%OPENJPA_HOME% 表示 OpenJPA 的安装目录,%JBOSS_HOME% 表示 JBoss 服务器的安装路径。
打包、发布 OpenJPA 应用
OpenJPA 应用开发完成后,需要打包成 EJB jar 后才能发布,我们可以使用 ANT 工具帮助我们完成 OpenJPA 应用的打包过程。另外,得益于 JBoss 服务器的热部署功能,我们也可以将 OpenJPA 应用的发布过程也使用 ANT 来完成。
[注]ANT 是 APACHE 软件基金会的一个开源项目,可以协助开发者自动完成项目的构建、测试、发布等工作。如何使用 ANT 请参考 ANT 项目的在线帮助文档。
实例中我们利用 ANT 来完成实体和 SessionBEAN 的编译、实体的 Enhance 工作、将实体和 SessionBean 打包成 EJB jar,并且将生成的 EJB jar 拷贝到 JBoss 服务器中对应目录下,JBoss 的热部署功能能够及时将生成 EJB jar 发布到服务器上。
用来打包、部署 OpenJPA 应用的 ANT 配置文件 build.xml 文件的全部内容如下:
2. <property name="src.dir" value="${basedir}" />
3. <!—设置 JBoss 服务器的安装路径à
4. <property name="jboss.home" value="C:\jboss-4.2.0.GA" />
5. <property name="build.dir" value="${basedir}/build" />
6. <property name="build.classes.dir" value="${build.dir}/classes" />
7.
8. <path id="classpath">
9. <fileset dir="${jboss.home}/lib">
10. <include name="**/*.jar" />
11. </fileset>
12. <fileset dir="${jboss.home}/server/default/lib">
13. <include name="**/*.jar" />
14. </fileset>
15. <fileset dir="${jboss.home}/server/default/deploy/ejb3.deployer">
16. <include name="*.jar" />
17. </fileset>
18. <fileset dir="${jboss.home}/server/default/deploy/jboss-aop-jdk50.deployer">
19. <include name="*.jar" />
20. </fileset>
21. <pathelement location="${build.classes.dir}" />
22. <pathelement location="${basedir}" />
23. </path>
24.
25. <property name="build.classpath" refid="classpath" />
26. <target name="prepare">
27. <mkdir dir="${build.dir}" />
28. <mkdir dir="${build.classes.dir}" />
29. </target>
30. <target name="compile" depends="prepare">
31. <javac srcdir="${src.dir}" destdir="${build.classes.dir}"
debug="on" deprecation="on" optimize="off" includes="**">
32. <classpath refid="classpath" />
33. </javac>
34. </target>
35. <!—完成实体的 Enhance à
36. <target name="enhance" depends="compile">
37. <!—OpenJPA 提供的完成 Enhance 的 ANT 任务à
38. <taskdef name="openjpac" classname="org.apache.openjpa.ant.PCEnhancerTask">
39. <classpath refid="classpath" />
40. </taskdef>
41.
42. <openjpac>
43. <!—设置需要 Enhance 的实体类à
44. <fileset dir="${build.classes.dir}">
45. <include name="**/beans/*.class" />
46. </fileset>
47. <classpath refid="classpath" />
48. </openjpac>
49. </target>
50. <!—打包、发布 EJB jar à
51. <target name="ejbjar" depends="enhance">
52. <jar jarfile="build/StatelessSample.jar">
53. <fileset dir="${build.classes.dir}">
54. <include name="**/*.*" />
55. </fileset>
56. </jar>
57. <copy file="build/StatelessSample.jar" todir="${jboss.home}/server/default/deploy" />
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
58. </target>
59.
60. <target name="run" depends="ejbjar">
61. <java classname="com.kuaff.ejb3.stateless.Client" fork="yes" dir=".">
62. <classpath refid="classpath" />
63. </java>
64. </target>
65.
66. <target name="clean.db">
67. <delete dir="${jboss.home}/server/default/data/hypersonic" />
68. </target>
69. <target name="clean">
70. <delete dir="${build.dir}" />
71. <delete file="${jboss.home}/server/default/deploy/StatelessSample.ejb3" />
72. </target>
73. </project>
简单的测试客户端
现在,实体 Animal 和封装实体操作的 SessionBean 都已经编译好并且发布到 JBoss 应用服务器上,我们可以编写一个简单的客户端来访问 SessionBean,测试对实体 Animal 的操作是否成功。
实例中,我们开发一个简单的 JSP 文件 Client.jsp,在 JSP 中,创建新的 Animal 对象,设置它的 name 属性为”警犬卡尔”,然后调用 SessionBean 的 persistAnimal 方法将这个实体持久化到数据库中。
Client.jsp 文件位于 %JBOSS_HOME%\server\default\deploy\jboss-web.deployer\ROOT.war 目录下,Client.jsp 文件的全部内容如下:
2. <%@ page import="javax.naming.NamingException" %>
3. <%@ page import="org.vivianj.jpademo.ILocalAnimalDAO" %>
4. <%@ page import="org.vivianj.jpademo.beans.Animal" %>
5.
6. <%
7. // 初始化 JNDI 上下文
8. InitialContext ctx;
9. ctx = new InitialContext();
10. // 获取 SessionBean 的本地接口
11. ILocalAnimalDAO animalDAO = (ILocalAnimalDAO) ctx.lookup("AnimalDAO/local");
12. // 创建新的 Animal 对象
13. Animal animal = new Animal();
14. animal.setName(" 警犬卡尔 ");
15. 调用 SessionBean 的业务方法将 Animal 对象持久化到数据库中
16. animalDAO.persistAnimal(animal);
17. %>
现在启动数据库服务器、JBoss 应用服务器,然后在浏览器地址栏中输入 http://localhost:8080/Client.jsp,然后查询数据库中的 Animal 表,里面应该有一条记录,它的 name 列的数据是”警犬卡尔”。
图 1. 运行 http://localhost:8080/Client.jsp 后 Animal 表的查询结果
总结
OpenJPA 框架符合 EJB3.0 规范中的 JPA 部分,因此 OpenJPA 既能够作为持久层框架独立运行,也能够被其他支持 EJB3.0 的 Java EE 容器集成后作为持久层框架。本文中以 JBoss 应用服务器为例,借助于一个简单的例子,详细地描述了如何在 JBoss 应用服务器环境下使用 OpenJPA 开发实体 Bean、并且用 SessionBean 封装实体 Bean 操作、在客户端通过 Local 接口访问的开发、部署过程。