6. 版本 2:OpenEJB / JPA 架构
6.1. 移植原则简介
本文将介绍将 JPA / Spring / Hibernate 应用程序移植为 JPA / OpenEJB / EclipseLink 应用程序时应遵循的原则。我们将在第 6.2 节中创建 Maven 项目。
6.1.1. 两种架构
当前基于 Spring / Hibernate 的实现
即将基于 OpenEJB / EclipseLink 构建的实现
6.1.2. 项目库
- [DAO] 和 [业务] 层不再由 Spring 实例化。它们由 OpenEJB 容器实例化。
- Spring 容器库及其配置已被 OpenEJB 容器库及其配置所取代。
- JPA / Hibernate 层库已被 JPA / EclipseLink 层的库所取代
6.1.3. JPA / EclipseLink / OpenEJB 层的配置
- 用于配置 JPA 层的 [META-INF/persistence.xml] 文件内容如下:
| <?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
<!-- entities JPA -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- the supplier JPA is EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- provider properties -->
<properties>
<property name="eclipselink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>
|
- 第 3 行:EJB 容器中的事务属于 JTA(Java 事务 API)类型。而在 Spring 中,它们属于 RESOURCE_LOCAL 类型。
- 第 9 行:使用的 JPA 实现是 EclipseLink
- 第 5–7 行:由 JPA 层管理的实体
- 第 11–13 行:EclipseLink 提供程序属性
- 第 12 行:每次执行时都会创建表
OpenEJB 容器所使用的 JTA 数据源的 JDBC 特性将由以下配置文件 [conf/openejb.conf] 指定:
| <?xml version="1.0"?>
<openejb>
<Resource id="Default JDBC Database">
JdbcDriver com.mysql.jdbc.Driver
JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
UserName root
</Resource>
</openejb>
|
- 第 3 行:当与嵌入在应用程序本身中的 OpenEJB 容器一起使用时,我们使用“默认 JDBC 数据库”ID。
- 第 5 行:我们使用 MySQL 数据库 [dbpam_eclipselink]
6.1.4. 使用 EJB 实现 [DAO] 层
- 实现 [DAO] 层的类将成为 EJB。让我们以 [CotisationDao] 类为例:
Spring 版本中的 [ICotisationDao] 接口如下:
| package dao;
import java.util.List;
import jpa.Cotisation;
public interface ICotisationDao {
// create a new contribution
Cotisation create(Cotisation cotisation);
// modify an existing contribution
Cotisation edit(Cotisation cotisation);
// delete an existing contribution
void destroy(Cotisation cotisation);
// search for a specific contribution
Cotisation find(Long id);
// get all objects Contribution
List<Cotisation> findAll();
}
|
该 EJB 将以两种不同形式实现此接口:本地接口和远程接口。本地接口可供在同一 JVM 中运行的客户端使用,而远程接口可供在不同 JVM 中运行的客户端使用。
本地接口:
| package dao;
import javax.ejb.Local;
@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}
|
- 第 6 行:[ICotisationDaoLocal] 接口继承自 [ICotisationDao] 接口,以采用其所有方法。它未添加任何新方法。
- 第 5 行:@Local 注解使其成为将要实现该接口的 EJB 的本地接口。
远程接口:
| package dao;
import javax.ejb.Remote;
@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
|
- 第 6 行:[ICotisationDaoRemote] 接口继承自 [ICotisationDao] 接口,以采用其所有方法。它未添加任何新方法。
- 第 5 行:@Remote 注解使其成为将要实现该接口的 EJB 的远程接口。
[DAO] 层由一个同时实现这两个接口的 EJB 来实现(这并非强制要求):
| @Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {
@PersistenceContext
private EntityManager em;
|
- 第 1 行:@Stateless 注解,该注解将该类定义为 EJB
- 第 2 行:@TransactionAttribute 注解,确保该类中的每个方法都在事务内执行。
- 第 5 行:@PersistenceContext 注解,将 JPA 层的 EntityManager 注入到 [CotisationDao] 类中。这与 Spring 版本中的实现完全一致。
当使用 [DAO] 层的本地接口时,该接口的客户端将在同一 JVM 中运行。
在上文中,[业务]层和[DAO]层通过引用交换对象。当一层修改共享对象时,另一层会察觉到这一变化。
当使用 [DAO] 层的远程接口时,该接口的客户端通常运行在另一个 JVM 中。
在上图中,[业务]层和[DAO]层通过值传递(即对交换对象进行序列化)来交换对象。当一层修改了共享对象时,另一层只有在收到该修改后的对象时,才会察觉到这一变化。
6.1.5. 使用 EJB 实现 [业务] 层
- 实现 [business] 层的类同时也成为一个 EJB,该 EJB 实现了本地和远程接口。最初的 [IMetier] 接口如下:
| package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// get your payslip
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// list of employees
List<Employe> findAllEmployes();
}
|
我们基于前面的接口创建一个本地接口和一个远程接口:
| package metier;
import javax.ejb.Local;
@Local
public interface IMetierLocal extends IMetier{
}
|
| package metier;
import javax.ejb.Remote;
@Remote
public interface IMetierRemote extends IMetier{
}
|
[业务]层中的EJB实现了这两个接口:
| @Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {
// reference to local [DAO] layers
@EJB
private ICotisationDaoLocal cotisationDao = null;
@EJB
private IEmployeDaoLocal employeDao = null;
@EJB
private IIndemniteDaoLocal indemniteDao = null;
|
- 第 1-2 行:定义一个 EJB,其中每个方法都在事务内运行。
- 第 7 行:对 EJB 本地接口 [CotisationDao] 的引用。
- 第 6 行:@EJB 注解指示 EJB 容器注入对 EJB 本地接口 [CotisationDao] 的引用。
- 第 8–11 行:我们对 EJB [EmployeeDao] 和 [CompensationDao] 的本地接口也进行同样的操作。
最终,当 [Metier] EJB 被实例化时,第 7、9 和 11 行中的字段将通过引用 [DAO] 层中这三个 EJB 的本地接口进行初始化。因此,我们在此假设 [business] 和 [DAO] 层将在同一 JVM 中运行。
6.1.6. EJB 客户端
在上图中,为了与[业务]层进行通信,[UI]层必须获取[业务]层中EJB的远程接口引用。
在上图中,[UI]层要与[业务]层进行通信,必须获取[业务]层中EJB的本地接口引用。获取这些引用的方法因容器而异。对于OpenEJB容器,您可以按以下步骤操作:
本地接口的引用:
| // configure the embedded Open EJB container
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// initialization of JNDI context with previous properties
InitialContext initialContext = new InitialContext(properties);
// instantiation layers DAO
employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
|
- 第 2–5 行:初始化 OpenEJB 容器。
- 第 5 行:我们有一个 JNDI(Java 命名和目录接口)上下文,它允许我们获取对 EJB 的引用。每个 EJB 都通过一个 JNDI 名称来标识:
- (待续)
- 对于本地接口,在 EJB 名称后添加 "Local"(第 7–9 行)
- 对于远程接口,我们在 EJB 名称后添加 "Remote"
在 Java EE 5 中,这些规则因 EJB 容器而异。这带来了一定的挑战。Java EE 6 引入了一种可在所有应用服务器上通用的 JNDI 命名规范。
上述代码是通过 EJB 的 JNDI 名称获取其本地接口的引用。我们之前提到,这些引用也可以通过 @EJB 注解获得。因此,我们可以这样编写:
@EJB
private IemployeDaoLocal employeDaoLocal ;
只有当 @EJB 注解属于由 EJB 容器加载的类时,该注解才会生效。例如,[Metier] 类就属于这种情况。然而,上面的代码属于一个控制台类,该类不会被 EJB 容器加载。因此,我们不得不使用 EJB 的 JNDI 名称。
以下是获取 [Metier] EJB 远程接口引用的代码:
| // configure the embedded Open EJB container
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// context initialization JNDI of container EJB
InitialContext initialContext = new InitialContext(properties);
// remote business layer instantiation
metier = (IMetierRemote) initialContext.lookup("MetierRemote");
|
6.2. 实践练习
我们建议将 NetBeans Spring/Hibernate 应用程序迁移到 OpenEJB/EclipseLink 架构。
当前基于 Spring / Hibernate 的实现
即将基于 OpenEJB / EclipseLink 构建的实现
6.2.1. 设置数据库 [ dbpam_eclipselink]
如果不存在,请创建 MySQL 数据库 [dbpam_eclipselink]。如果存在,请删除其中的所有表。按照第 6.2.1 节中的说明,创建一个连接到此数据库的 NetBeans 连接。
6.2.2. NetBeans 项目的初始配置
- 加载 Maven 项目 [mv-pam-spring-hibernate]
- 创建一个新的 Maven Java 项目 [mv-pam-openejb-eclipselink] [1]
- 在 [文件] 选项卡 [2] 中,在项目根目录下创建一个 [conf] 文件夹 [3]
- 将以下 [openejb.conf] 文件 [4] 放置在此文件夹中:
| <?xml version="1.0"?>
<openejb>
<Resource id="Default JDBC Database">
JdbcDriver com.mysql.jdbc.Driver
JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
UserName root
</Resource>
</openejb>
|
- 创建文件夹 [src/main/resources/META-INF] [5]
- 将以下 [persistence.xml] 文件 [6] 放入其中:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
<!-- the supplier JPA is EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- jpa entities -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- properties provider EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
- 第 12 行:我们向 EclipseLink 请求细粒度日志,
- 第 13 行:在 JPA 层实例化时将创建表,
- 将 OpenEJB 和 EclipseLink 库以及 MySQL JDBC 驱动程序添加到项目的 [pom.xml] 文件中:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-openejb-eclipselink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-openejb-eclipselink</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>openejb-core</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
<repositories>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library Library[eclipselink]</name>
</repository>
</repositories>
</project>
- 第 18–22 行:OpenEJB 依赖项,
- 第 30–39 行:EclipseLink 依赖项,
- 第 41–44 行:MySQL JDBC 驱动程序依赖项
6.2.3. 移植 [DAO] 层
我们将通过将 [mv-pam-spring-hibernate] 项目中的包复制到 [mv-pam-openejb-eclipselink] 项目中,来移植 [DAO] 层。
- 复制 [dao, exception, jpa] 包
上述错误是由于复制的 [DAO] 层使用了 Spring,而 Spring 库已不再属于该项目。
6.2.3.1. EJB [CotisationDao]
我们为未来的 EJB [CotisationDao] 创建本地和远程接口:
本地接口 ICotisationDaoLocal:
| package dao;
import javax.ejb.Local;
@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}
|
为确保导入的包正确,请右键单击代码并选择 [修复导入]。
远程接口 ICotisationDaoRemote:
| package dao;
import javax.ejb.Remote;
@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
|
然后,我们将 [CotisationDao] 类修改为 EJB:
| ...
import javax.persistence.PersistenceContext;
import jpa.Cotisation;
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {
@PersistenceContext
private EntityManager em;
...
|
该类对 Spring 框架的导入已消失。请对项目执行 [清理并构建]:
在[1]中,[CotisationDao]类中不再有任何错误。
6.2.3.2. EJB [EmployeDao] 和 [IndemniteDao]
我们对 [DAO] 层的其他元素重复相同的处理过程:
- 从 IEmployeDao 派生的接口 IEmployeDaoLocal 和 IEmployeDaoRemote
- 实现这两个接口的 EJB `EmployeDao`
- 从 IIndemniteDao 派生的接口 IIndemniteDaoLocal 和 IIndemniteDaoRemote
- EJB `IndemniteDao` 实现了这两个接口
完成上述操作后,项目中不再出现错误 [2]。
6.2.3.3. [PamException] 类
[PamException] 类与之前保持一致,仅有一处细微改动:
| package exception;
import javax.ejb.ApplicationException;
@ApplicationException(rollback=true)
public class PamException extends RuntimeException {
// error code
private int code;
...
|
已添加第 5 行。要获取正确的导入语句,请单击 [修复导入]。
要理解第 5 行中的注解,请记住我们 [DAO] 层中 EJB 的每个方法:
- 都在由 EJB 容器启动并结束的事务中运行
- 一旦发生错误,会立即抛出 [PamException]
当[业务]层调用[DAO]层的方法M时,该调用会被EJB容器拦截。这就像在[业务]层和[DAO]层之间存在一个中间类——此处称为[EJB代理]——拦截所有对[DAO]层的调用。 当对 [DAO] 层方法 M 的调用被拦截时,EJB 代理会启动一个事务,然后将控制权移交给 [DAO] 层的方法 M,该方法随后将在该事务内执行。方法 M 可能伴随异常完成,也可能不抛出异常。
- 如果方法 M 正常完成(未抛出异常),控制权将返回给 EJB 代理,由其通过提交事务来终止该事务。随后,控制权将返回给 [业务] 层中的调用方法
- 如果方法 M 因抛出异常而终止,执行将返回 EJB 代理,该代理通过回滚事务来终止事务。此外,它会将此异常包装为 EJBException。 随后执行返回至 [business] 层的调用方法,该方法因此会收到一个 EJBException。上文第 5 行中的注解阻止了这种封装。因此,[business] 层将收到一个 PamException。此外,rollback=true 属性指示 EJB 代理:当其收到 PamException 时,必须回滚事务。
6.2.3.4. 测试 [DAO] 层
由 EJB 实现的 [DAO] 层可以进行测试。首先,我们将 [mv-pam-springhibernate] 项目中 [Test Packages] 下的 [dao] 包复制到当前正在开发的项目中 [1]:
我们仅保留 [JUnitInitDB] 测试,该测试会使用一些数据初始化数据库 [2]。我们将 [JUnitInitDbLocal] 类重命名为 [JUnitInitDBLocal] [3]。该 [JUnitInitDBLocal] 类将使用 [DAO] 层 EJB 的本地接口。
首先,我们将 [JUnitInitDBLocal] 类修改如下:
| public class JUnitInitDBLocal {
static private IEmployeDaoLocal employeDao = null;
static private ICotisationDaoLocal cotisationDao = null;
static private IIndemniteDaoLocal indemniteDao = null;
@BeforeClass
public static void init() throws Exception {
// configure the embedded Open EJB container
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// initialization of JNDI context with previous properties
InitialContext initialContext = new InitialContext(properties);
// instantiation of local DAO layers
employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
}
|
- 第 3-5 行:引用 [DAO] 层中 EJB 的本地接口
- 第 7 行:@BeforeClass 注解用于标记 JUnit 测试开始时执行的方法
- 第10-13行:初始化OpenEJB容器。此初始化过程具有专有性,且因EJB容器而异。
- 第 13 行:我们有一个 JNDI(Java 命名和目录接口)上下文,允许通过名称访问 EJB。在 OpenEJB 中,EJB 的本地接口由 ELocal 表示,远程接口由 ERemote 表示。
- 第 15–17 行:我们从 JNDI 上下文中请求 EJB [EmployeDao、CotisationDao、IndemniteDao] 的本地接口引用。
构建项目,如有必要请启动 MySQL 服务器,然后运行 JUnitInitDBLocal 测试。请注意,[persistence.xml] 文件已配置为在每次运行时重新创建表。在运行测试之前,最好先删除 MySQL 数据库 [dbpam_eclipselink] 中的所有表。
- 在 [1] 的 [Services] 选项卡中,删除第 6.2.1 节中建立的 NetBeans 连接中的表。
- 在 [2] 中,[dbpam_eclipselink] 数据库中不再包含任何表
- 在 [3] 中,项目已构建
- 在 [4] 中,执行了 JUnitInitDBLocal 测试
- 在 [5] 中,测试通过
- 在 [6] 中,刷新 NetBeans 连接
- 在 [7] 中,我们可以看到由 JPA 层创建的 4 张表。该测试的目的是向这些表中插入数据。我们查看其中一张表的内容
OpenEJB 容器在控制台中显示了日志:
- 第 2-3 行:EJB [CotisationDaoLocal] 的两个 JNDI 名称,
- 第 4-5 行:EJB [CotisationDaoRemote] 的两个 JNDI 名称,
- 第 7-8 行:EJB [EmployeDaoLocal] 的两个 JNDI 名称,
- 第 9-10 行:EJB [EmployeDaoRemote] 的两个 JNDI 名称,
- 第 12-13 行:EJB [IndemniteDaoLocal] 的两个 JNDI 名称,
- 第 14–15 行:EJB [EmployeeDaoRemote] 的两个 JNDI 名称。
我们再次运行相同的测试,这次使用 EJB 的远程接口。
在 [1] 中,[JUnitInitDBLocal] 类已被复制(复制/粘贴)到 [JUnitInitDBRemote] 中。在此类中,我们将本地接口替换为远程接口:
| Infos - PersistenceUnit(name=dbpam_eclipselinkPU, provider=org.eclipse.persistence.jpa.PersistenceProvider) - provider time 396ms
Infos - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao) --> Ejb(deployment-id=IndemniteDao)
Infos - existing thread singleton service in SystemInstance() org.apache.openejb.cdi.ThreadSingletonServiceImpl@624a240d
Infos - OpenWebBeans Container is starting...
Infos - Adding OpenWebBeansPlugin : [CdiPlugin]
Infos - All injection points were validated successfully.
Infos - OpenWebBeans Container has started, it took [70] ms.
Infos - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Deployed Application(path=D:\data\istia-1112\netbeans\glassfish\mv-pam\tmp\mv-pam-openejb-eclipselink\classpath.ear)
|
完成上述操作后,即可运行新的测试类。在此之前,请使用 NetBeans 连接 [dbpam_eclipselink],从数据库 [dbpam_eclipselink] 中删除相关表。
使用 NetBeans 连接 [dbpam_eclipselink],验证数据库是否已填充数据。
6.2.4. 移植 [业务] 层
我们将通过将 [mv-pam-spring-hibernate] 项目中的包复制到 [mv-pam-openejb-eclipselink] 项目中,来移植 [业务] 层。
上文[1]中报告的错误是由于复制的[business]层使用了Spring,而该项目中已不再包含Spring库。
6.2.4.1. [业务] EJB
我们遵循与 [CotisationDao] EJB 描述相同的步骤。首先,在 [2] 中,我们为未来的 [Metier] EJB 创建本地和远程接口。两者均继承自初始的 [IMetier] 接口。
| public class JUnitInitDBRemote {
static private IEmployeDaoRemote employeDao = null;
static private ICotisationDaoRemote cotisationDao = null;
static private IIndemniteDaoRemote indemniteDao = null;
@BeforeClass
public static void init() throws Exception {
// configure the embedded Open EJB container
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// initialization of JNDI context with previous properties
InitialContext initialContext = new InitialContext(properties);
// instantiation of remote DAO layers
employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
}
|
| package metier;
import javax.ejb.Local;
@Local
public interface IMetierLocal extends IMetier{
}
|
完成上述操作后,在[3]中,我们将[Business]类修改为EJB:
| package metier;
import javax.ejb.Remote;
@Remote
public interface IMetierRemote extends IMetier{
}
|
- 第 1 行:@Stateless 注解将该类定义为 EJB
- 第 2 行:该类的每个方法都将在事务内执行
- 第 3 行:[Metier] EJB 同时实现了我们刚刚定义的本地接口和远程接口
- 第 7 行:[Metier] EJB 将通过其本地接口调用 [CotisationDao] EJB。这意味着 [业务] 层和 [DAO] 层必须在同一个 JVM 中运行。
- 第 6 行:@EJB 注解确保 EJB 容器注入对 [CotisationDao] EJB 本地接口本身的引用。我们遇到的另一种方法是使用 JNDI 上下文。
- 第 8–11 行:[DAO] 层中的另外两个 EJB 也采用了相同的机制。
6.2.4.2. 测试 [business] 层
由 EJB 实现的 [business] 层可以进行测试。首先,我们将 [mv-pam-spring-hibernate] 项目中 [Test Packages] 下的 [business] 包复制到当前正在构建的项目 [1] 中:
- 在 [1] 中,复制后的结果
- 在 [2] 中,我们删除第一个测试
- 在 [3] 中,剩余的测试被重命名为 [JUnitMetierLocal]
[JUnitMetierLocal] 类变为如下形式:
| @Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {
// references to local [DAO] layer
@EJB
private ICotisationDaoLocal cotisationDao = null;
@EJB
private IEmployeDaoLocal employeDao = null;
@EJB
private IIndemniteDaoLocal indemniteDao = null;
// get your payslip
public FeuilleSalaire calculerFeuilleSalaire(String SS,
double nbHeuresTravaillées, int nbJoursTravaillés) {
// retrieve employee information
...
|
- 第 4 行:对 EJB [Metier] 的本地接口的引用
- 第 8-12 行:OpenEJB 容器配置与 [DAO] 层测试中使用的配置完全相同
- 第15–19行:我们在第12行从JNDI上下文中请求对[DAO]层中三个EJB以及[business]层中一个EJB的引用。[DAO]层中的EJB将用于初始化数据库,而[business]层中的EJB将用于执行薪资计算测试。
运行 [JUnitMetierLocal] 测试得到以下结果 [1]:
在 [2] 中,我们将 [JUnitMetierLocal] 复制为 [JUnitMetierRemote],以便本次测试 [Metier] EJB 的远程接口。[JUnitMetierRemote] 的代码已修改为使用该远程接口,其余部分保持不变。
| public class JUnitMetierLocal {
// local business layer
static private IMetierLocal metier;
@BeforeClass
public static void init() throws NamingException {
// configure the embedded Open EJB container
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// initialization of JNDI context with previous properties
InitialContext initialContext = new InitialContext(properties);
// instantiation of local DAO layers
IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
// local business layer instantiation
metier = (IMetierLocal) initialContext.lookup("MetierLocal");
// empty the base
...
}
|
- 第 4 行和第 19 行:我们使用 [Business] EJB 的远程接口。
- 第 15–17 行:我们使用 [DAO] 层的远程接口
- 第 34–35 行:由于使用远程接口时,客户端与服务器之间交换的对象是按值传递的,因此我们必须获取 create(Indemnite i) 方法返回的结果。而在使用本地接口时,对象是按引用传递的,因此无需此操作。
完成上述操作后,即可构建项目并执行 [JUnitMetierRemote] 测试:
6.2.5. 移植 [console] 层
我们将通过将 [mv-pam-spring-hibernate] 项目中的包复制到 [mv-pam-openejb-eclipselink] 项目中,来移植 [console] 层。
上文[1]中报告的错误源于:复制的[business]层使用了Spring,而该项目中已不再包含Spring库。在[2]中,[Main]类已重命名为[MainLocal]。它将使用[Business] EJB的本地接口。
[MainLocal]类的代码更改如下:
| public class JUnitMetierRemote {
// remote business layer
static private IMetierRemote metier;
@BeforeClass
public static void init() throws NamingException {
// configure the embedded Open EJB container
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// initialization of JNDI context with previous properties
InitialContext initialContext = new InitialContext(properties);
// instantiation of remote DAO layers
IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
// remote business layer instantiation
metier = (IMetierRemote) initialContext.lookup("MetierRemote");
// empty the base
for(Employe employe:employeDao.findAll()){
employeDao.destroy(employe);
}
for(Cotisation cotisation:cotisationDao.findAll()){
cotisationDao.destroy(cotisation);
}
for(Indemnite indemnite : indemniteDao.findAll()){
indemniteDao.destroy(indemnite);
}
// fill it
Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
indemnite1=indemniteDao.create(indemnite1);
indemnite2=indemniteDao.create(indemnite2);
employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
}
}
|
更改位于第 13–25 行。这就是我们获取 [business] 层引用(第 17–22 行)的方式,该层已发生变更。我们不再解释新代码,因为之前示例中已涵盖相关内容。完成这些更改后,项目不再存在任何错误(参见 [3])。
我们将项目配置为带参数运行 [1]:
为了使控制台应用程序正常运行,数据库中必须有数据。为此,您必须修改 [META-INF/persistence.xml] 文件:
| public static void main(String[] args) {
// local data
final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
...
// mistakes?
if (erreurs.size() != 0) {
for (int i = 0; i < erreurs.size(); i++) {
System.err.println(erreurs.get(i));
}
return;
}
// it's OK - we can ask for the payslip at the [trade] layer
IMetierLocal metier = null;
FeuilleSalaire feuilleSalaire = null;
try {
// configure the embedded Open EJB container
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// initialization of JNDI context with previous properties
InitialContext initialContext = new InitialContext(properties);
// local business layer instantiation
metier = (IMetierLocal) initialContext.lookup("MetierLocal");
// wage sheet calculation
feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
} catch (PamException ex) {
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
return;
} catch (Exception ex) {
System.err.println("L'erreur suivante s'est produite : " + ex.toString());
return;
}
// detailed display
String output = "Valeurs saisies :\n";
output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
....
|
第 14 行代码(该行导致每次运行时都会重建数据库表)已被注释掉。必须重新构建项目(执行“清理”和“构建”)才能使此更改生效。完成上述操作后,即可运行程序。如果一切正常,控制台输出将类似于以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
<!-- the supplier JPA is EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- jpa entities -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- properties provider EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<!--
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
-->
</properties>
</persistence-unit>
</persistence>
在此,我们使用了 [business] 层的本地接口。现在,我们在第二个控制台类中使用其远程接口:
在[1]中,[MainLocal]类已被复制为[MainRemote]。对[MainRemote]中的代码进行了修改,使其使用[business]层的远程接口:
| .......
INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
INFO - Deployed Application(path=classpath.ear)
[EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse Persistence Services - 1.1.2.v20090612-r4475
[EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-metier-dao-openejb-eclipselink-0910/build/classes/-jpa login successful
Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20
Informations Employé :
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2
Informations Cotisations :
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %
Informations Indemnités :
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %
Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro
BUILD SUCCESSFUL (total time: 4 seconds)
|
第2行和第8行已进行修改。该项目已配置[2]为运行[MainRemote]类。运行后结果与之前相同。
6.3. 结论
我们已经演示了如何将 Spring/Hibernate 架构迁移到 OpenEJB/EclipseLink 架构。
Spring/Hibernate 架构
OpenEJB/EclipseLink 架构
由于原始应用程序采用分层结构,因此移植过程非常顺利。这一点非常重要,需要理解。