企业级JavaBean开发全解析
1. 创建选择方法
在开发过程中,有时需要创建特定的选择方法。以员工实体为例,创建一个名为 selectAll 的选择方法,其属性如下:
- 选择名称: selectAll
- 返回类型: java.util.Collection
- 参数:留空
- 结果类型映射:本地
- 查询语句: SELECT OBJECT(e) FROM Employee AS e
大部分员工实体的代码可由EJB向导生成,但还需添加 CalculateTotalSalary 方法的实现。具体步骤为:定位 EmployeeBean 类并双击查看源码,再找到 ejbHomeCalculateTotalSalary() 方法,按如下代码实现:
public java.math.BigDecimal ejbHomeCalculateTotalSalary() {
float res = 0;
try {
java.util.Iterator iter = ejbSelectAll().iterator();
while(iter.hasNext()){
Employee emp = (Employee)iter.next();
res = res + emp.getSalary().floatValue();
}
}
catch (FinderException ex) {
throw new javax.ejb.EJBException(ex);
}
return new java.math.BigDecimal(new Float(res).toString());
}
2. 事务处理
事务在大多数应用开发项目中都扮演着重要角色,在基于EJB的解决方案开发中也不例外。事务是一组代表单个单元或任务的工作,这个单元可能简单到只有一步,也可能包含多个复杂步骤。使用事务的原因在于它能减轻跨多个客户端协调数据或资源访问的复杂性。
事务有四个主要特性,通常被称为ACID,具体如下表所示:
| 属性 | 描述 |
| — | — |
| 原子性 | 事务的所有效果要么全部保留,要么全部不保留。 |
| 一致性 | 事务保持数据的完整性和一致性。 |
| 隔离性 | 事务的中间结果在事务外部不可见。 |
| 持久性 | 已提交的效果能在系统故障后存活。 |
Enterprise JavaBeans定义了七种不同类型的事务属性,每种属性的实现略有不同,具体如下表:
| 事务属性 | 描述 |
| — | — |
| Required | 保证方法执行的工作在全局事务上下文中。如果调用者已有事务上下文,容器使用相同上下文;若调用者未分配事务上下文,容器会自动开启新事务。该属性便于实现多个Bean,并使用相同全局事务协调所有Bean的工作。 |
| RequiresNew | 容器使用新开启的事务调用企业Bean方法,调用完成后结束该事务,可通过回滚或提交请求结束。 |
| Mandatory | 允许Bean方法声明必须由客户端在事务中调用,通常这类方法并非单独使用,而是作为更大事务的一部分。 |
| Supports | Bean参与交互时不启动新事务,但如果存在事务则使用。只有在有事务和无事务情况下都能正常运行的应用逻辑才能使用此模式。 |
| NotSupported | 允许实现中不能或不应在事务中调用的Bean方法。例如,Bean不希望客户端的事务传播到其处理过程中可能接触的资源。 |
| Never | 允许Bean方法声明绝不能由运行事务的客户端调用,通常是因为该方法能提供ACID属性。此属性在EJB 2.0规范中已被弃用。 |
| Bean - managed | 也称为编程式事务控制,即事务在Bean的源代码中实现。 |
在容器中使用事务需在部署描述符中指定,JBuilder的EJB设计器提供了一个易用的界面来访问事务属性。以员工实体Bean为例,定义事务上下文的步骤如下:
1. 选择一个Bean来定义事务属性。
2. 在员工实体下会看到一个名为“Container Transaction”的子节点。
3. 双击“Container Transaction”,加载容器事务编辑器。
4. 使用下拉组合框选择接口(Home、Remote、LocalHome或Local),选择接口后,可选择所有方法(*)或指定某个方法。
5. 最后选择合适的事务属性。
以下是部署描述符中包含事务属性的部分示例:
<container-transaction>
<method>
<description />
<ejb-name>Employee</ejb-name>
<method-intf>LocalHome</method-intf>
<method-name>create</method-name>
<method-params>
<method-param>java.lang.Short</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<description />
<ejb-name>Employee</ejb-name>
<method-intf>LocalHome</method-intf>
<method-name>findAll</method-name>
<method-params />
</method>
<trans-attribute>Supports</trans-attribute>
</container-transaction>
3. 容器安全
在所有基于企业级应用的开发和部署中,安全都是一个重要问题。EJB框架通过容器支持安全功能,使安全实现变得轻松。EJB安全模型专为企业级应用设计,若不使用EJBs则不适用。实现EJB安全通常需要以下步骤:
- 定义用户和组。
- 将应用资源与用户或组关联。
- 提供高效且不同的安全维护方法。
- 实现运行时验证安全的逻辑。
- 实现管理用户、组和权限的工具。
3.1 身份验证
身份验证是验证客户端是否为其声称的身份的过程,是安全模型的基础。大多数EJB容器提供多种身份验证机制,例如Borland的企业服务器支持使用JDBC、LDAP、JDatastore进行身份验证,也可实现自定义安全类。
3.2 授权
授权是为底层实现赋予权利的过程。例如,调用某个方法可能需要特定的权限集。身份验证可通过容器实现,也可在Bean的主接口中以编程方式实现。以下是服务器端身份验证的序列图示意:
sequenceDiagram
participant Client
participant Authentication
participant Security Realm
participant EJBObject
participant Authorizer
participant Bean Instance
Client->>Authentication: connect(user, pass)
Authentication->>Security Realm: Authenticate(user, pass)
Client->>EJBObject: Invoke(“withdraw”, amount)
EJBObject->>Authorizer: authorize(“withdraw”)
Authorizer->>Bean Instance: withdraw(amount)
Bean Instance->>Bean Instance: withdraw(amount)
3.3 安全通信
安全通信实现起来看似简单,但细节却很复杂。JBuilder本身不提供管理或实现安全通信的特殊功能,这主要是容器的特性。例如,Borland企业服务器可通过容器控制台配置SSL,实现客户端之间或与其他J2EE服务器之间的安全通信。
JBuilder对安全的支持分为两部分:一是能够定义安全编辑器可用的角色;二是能够根据最终用户的角色为任何接口或方法分配安全权限。
4. 设计准则
在使用实体Bean时,有两个重要的设计考虑因素:
4.1 使用本地接口而非远程接口
远程访问数据时,数据需要从一个位置移动到另一个位置,因此需要进行序列化。序列化过程成本高,包括序列化数据、通过网络传输以及在客户端重新实例化数据的延迟可能很耗时。EJB 2.0规范引入了使用本地接口调用和与实体Bean通信的功能。本地接口的灵感源于解决与序列化数据相关的性能问题,通过传递数据引用而非值,避免了数据的复制和序列化。但使用本地接口也有缺点,即失去了网络普遍性,客户端需要知道要使用的Bean的位置,但能获得更高的性能吞吐量。
4.2 优先使用容器管理持久化(CMP)而非Bean管理持久化(BMP)
早期使用J2EE服务器时,让容器管理所有持久化的想法可能不太容易接受,因此很多人会自己开发持久化代码。但这种方式对于开发大规模应用既不理想也不高效,因为会编写大量重复的代码,只是SQL语句和成员属性的细节有所不同。随着EJB规范从1.1到2.0的变化,容器管理持久化变得更加重要和强大。容器管理持久化支持企业应用的大多数持久化需求,使用容器完成这些任务不仅开发速度快,而且性能通常更好,同时也是J2EE架构中最具可移植性的组件。
5. 实践应用
5.1 构建持久化层
使用JBuilder创建持久化层,前提是要有合适的数据结构。以ChalkTalk应用为例,创建实体Bean来管理持久化的步骤如下:
1. 在包含会话Bean的EJB模块中,从数据源导入模式。在EJB设计器中右键单击数据源节点,选择“Import Schema from Datasource”。
2. 设置以下数据库参数:
- 驱动: com.borland.datastore.jdbc.DataStoreDriver
- URL: jdbc:borland:dslocal:C:\JBDG\db\ChalkTalk.jds
- 用户名:留空
- 密码:留空
3. 右键单击每个表,为每个表创建一个CMP 2.0实体Bean。例如,右键单击“Room”表,创建一个CMP 2.0 Bean。
4. 定义表之间的关系。
5.2 配置实体Bean
通常,每个创建的实体Bean需要的查找方法不止 findByPrimaryKey 。以 Room 实体Bean为例,添加查找方法的步骤如下:
1. 右键单击 Room 实体Bean,添加一个查找方法。
2. 配置查找方法的以下属性:
- 名称: findAll
- 返回类型: java.util.Collection
- 输入参数:留空
- 主接口: Local Home
- 查询语句: SELECT OBJECT(o) FROM RoomSchema AS o
3. 编译并保存。
5.3 创建EJB连接工厂
EJB连接工厂为本地和远程接口提供缓存功能,并且可以使接口具有普遍性。创建EJB连接工厂的步骤如下:
1. 在项目中创建一个新类,命名为 EJBConnectionFactory 。
2. 将新创建的类移动到 com.sams.chalktalk.beans 包中。
3. 添加以下代码来实现与本地和远程Bean的连接,并在建立连接后将它们放入缓存:
package com.sams.chalktalk.beans;
import javax.naming.*;
import java.util.*;
import javax.ejb.*;
class EJBHomeFactory {
private static EJBHomeFactory instance = null;
private Context initialContext;
private Map ejbHomes;
private EJBHomeFactory() throws NamingException {
initialContext = new InitialContext();
ejbHomes = Collections.synchronizedMap(new HashMap());
}
public static EJBHomeFactory getInstance() throws NamingException {
if(instance == null) {
instance = new EJBHomeFactory();
}
return instance;
}
public EJBLocalHome lookupByLocalEJBReference(String ejbReferenceComponent)
throws NamingException {
java.lang.Object home = ejbHomes.get(ejbReferenceComponent);
if(home == null) {
home = initialContext.lookup("java:comp/env/ejb/" +
ejbReferenceComponent);
ejbHomes.put(ejbReferenceComponent, home);
}
return (EJBLocalHome) home;
}
public EJBHome lookupByRemoteEJBReference(String ejbReferenceComponent,
Class homeClass)
throws NamingException {
java.lang.Object home = ejbHomes.get(ejbReferenceComponent);
if(home == null) {
java.lang.Object obj =
initialContext.lookup("java:comp/env/ejb/" + ejbReferenceComponent);
home = javax.rmi.PortableRemoteObject.narrow(obj, homeClass);
ejbHomes.put(ejbReferenceComponent, home);
}
return (EJBHome) home;
}
}
- 编译并保存项目。
5.4 创建管理Bean
管理Bean用于展示业务逻辑,并最终通过实体Bean与持久化层交互。创建 RoomManager 管理Bean的步骤如下:
1. 创建一个新类,命名为 RoomManager ,并将其放在 com.sams.chalktalk.beans 包中。
2. 添加以下代码来展示所需的业务逻辑:
package com.sams.chalktalk.beans;
import javax.naming.*;
import javax.ejb.*;
import java.util.*;
class RoomManager {
//Add a home factory for caching the home interfaces
private EJBHomeFactory homeFactory;
private static RoomManager instance = null;
//Use the RoomValueFactory to create
private RoomValueFactory roomValueFactory;
private RoomManager() throws NamingException {
homeFactory = EJBHomeFactory.getInstance();
}
public static RoomManager getInstance() throws NamingException {
if(instance == null) {
instance = new RoomManager();
}
return instance;
}
public String createRoom(RoomValue roomValue)
throws FinderException, NamingException {
try {
RoomHome roomHome =
(RoomHome) homeFactory.lookupByLocalEJBReference("Room");
Room room = roomHome.create(roomValue.getLocation(),roomValue.
getCapacity()
,roomValue.getName(),roomValue.getKey());
return room.getName();
}
catch(Exception e) {
throw new EJBException(e);
}
}
public void updateRoom(RoomValue roomValue)
throws FinderException, NamingException {
try {
Room room = roomValueFactory.findRoom(roomValue.getKey());
room.setCapacity(roomValue.getCapacity());
}
catch(Exception e) {
throw new EJBException(e);
}
}
public void removeRoom(Short key)
throws FinderException, NamingException {
try {
Room room = roomValueFactory.findRoom(key);
room.remove();
}
catch(Exception e) {
throw new EJBException(e);
}
}
public void removeAllRooms()
throws FinderException, NamingException {
try {
Collection rooms = roomValueFactory.findAllRooms();
Iterator iterator = rooms.iterator();
Room room = null;
while(iterator.hasNext()) {
room = (Room) iterator.next();
room.remove();
}
}
catch(Exception e) {
throw new EJBException(e);
}
}
}
5.5 附加会话外观
会话外观将多个管理Bean包装成一个会话Bean,通过这个会话Bean可以访问ChalkTalk系统的所有功能。附加会话外观的步骤如下:
1. 为要使用的每种类型的Bean管理定义一个私有成员。例如,为 RoomManager 定义:
private RoomManager roomManager;
- 在
ejbCreate()方法中创建管理Bean的实例。以RoomManager为例:
public void ejbCreate() throws CreateException {
try {
roomManager = RoomManager.getInstance();
}
catch (NamingException ex) {
//pass exception to the container
throw new EJBException(ex);
}
}
- 将管理Bean的调用附加到相应的会话Bean方法中。如果客户端调用会话Bean的
createRoom方法,需要将信息传递给管理类。示例代码如下:
public void createRoom(RoomValue room) {
try {
roomManager.createRoom(room);
}
catch (Exception ex) {
throw new EJBException(ex);
}
}
- 编译并保存项目。
5.6 测试实现
测试会话外观的最简单方法是构建一个专门用于测试接口的客户端。构建测试客户端的步骤如下:
1. 从JBuilder菜单中选择“File” -> “New”。
2. 在对象库中,在“Enterprise”选项卡下选择“EJB Test Client”。
3. 指定以下参数来构建测试客户端:
- EJB名称: ChalkTalkFacade
- 包: com.sams.chalktalk.client
- 类名: ChalkTalkFacadeTestClient
- 为测试远程接口调用生成带参数的方法:勾选
- 生成日志消息:勾选
- 生成主函数:勾选
- 生成头部注释:勾选
4. 在主方法中添加以下代码来测试会话Bean接口:
//create a Remote interface
client.create();
//Add a room
client.addRoom(new RoomData(5,"Small Room",5,"2nd Floor"));
//Add with invalid capacity
client.addRoom(new RoomData(3,"Error Room",0,"3rd Floor"));
- 保存、编译并运行测试应用程序。
6. 总结与反思
实体Bean旨在处理大多数企业应用的所有持久化需求,具有持久性、事务性、多用户、长生命周期以及能在容器生命周期之外存活等特点。更高级的容器管理持久化和EJB规范中添加的其他重要功能,使应用的构建和管理更加高效。
在实际应用中,开发者需要思考以下问题:
- 应该使用容器管理持久化还是Bean管理持久化?
- 应该设计使用本地接口还是远程接口?
- 实体Bean是否应该用于封装非传统的数据访问?
通过对这些问题的思考和实践,开发者可以更好地利用实体Bean来构建高效、安全的企业级应用。
超级会员免费看
1242

被折叠的 条评论
为什么被折叠?



