1 支持事务
EJB架构的关键特性之一就是对分布式事务的支持。EJB架构允许应用开发者原子地更新分布在不同位置的多个数据库的数据。而且,这些位置上可以使用不同的EJB服务器。
1.1 概述
本章对事务做了简单的概述,并阐述了大量的EJB中的事务场景。
1.1.1 事务
事务被证明是简化应用开发的一个技术。事务使得应用开发者脱离了处理复杂的失败恢复和多用户并发问题。如果应用开发者使用事务,则开发者致力于调用的事务单元。事务系统保证这个工作单元要么完全提交,要么完全回滚。进一步说,事务使得应用开发者设计的应用好像运行在一个连续地执行工作单元的环境之中。
对事务的支持是EJB架构的关键特性。EJB提供商和客户应用开发者隐藏分布式事务的复杂性。EJB提供者可以选择由程序控制的事务分隔(称为bean管理的事务分隔),也可以选择由容器自动执行的声明式事务分隔(称为容器管理的事务分隔)。
对于bean管理的事务分隔,企业bean用javax.transaction.UserTransaction接口来分隔事务。在UserTransaction.begin和UserTransaction.commit之间调用的对所有资源的存取都属于事务的一部分。
在本章中使用的术语资源和资源管理器都是指在企业bean
类上用Resource
注释符或在配置文件中用resource-ref
元素声明的资源。它不仅仅指数据库资源,也指其他资源,比如JMS
连接。这些资源都被认为是由容器管理的资源。(注意:也包含用Resource
注释符或resource-ref
元素指定的但不是资源的环境条目)
对于容器管理的事务分隔,容器根据开发者在元数据注释符或在配置元素中指定的指令来分隔事务。这些指令称为事务属性,来告诉容器是否在客户事务中执行EJB方法,还是在新事务中执行,还是不使用事务(参考13.6.5章节来了解不使用事务的情况)。
不管企业bean使用bean管理的事务还是使用容器管理的事务分隔,实现事务管理是EJB容器和服务提供商的重任。EJB容器和服务实现必需的底层事务协议,例如事务管理器和数据库系统/消息提供者之间的两阶段提交协议、事务上下文传递和分布式两阶段提交。
许多应用都会有多个企业bean
,这些bean
都使用一个资源管理器(典型地是一个关系数据库管理系统)。在不使用分布式事务时,EJB
容器可以优先使用资源管理的本地事务。资源管理器的本地事务不涉及与外部事务管理器的控制或协同。容器使用本地事务作为企业bean
的优先事务技术对企业bean
是不可见的,不管这些bean
是容器管理事务分隔还是bean
管理事务分隔的事务。对于使用资源管理器本地事务作为容器的优先事务策略的讨论,可以参考《Java Platform
,Enterprise Edition(Java EE), http://jcp.org/en/jsr/detail?id=244
》和《Java2 Enterprise Edition Connector Architecture, v1.5. http://java.sun.com/j2ee/connector
》。
1.1.2 事务模型
EJB架构支持平面事务。平面事务不能有任何子事务(嵌套事务)。
注意:不支持嵌套事务的决定可以让现存的事务处理提供商和数据库管理系统都能对EJB
提供事务支持。如果将来这些提供商对嵌套事务提供了支持,那么EJB
可以提升到支持嵌套事务。
1.1.3 JTA和JTS的关系
JTA是事务管理器和其他涉及到分布式事务处理系统的部分之间的接口,这些部分包括:应用程序,资源管理器和应用服务器。
JTS API是CORBA OTS(Object Transaction Service)1.1规范与java的结合。JTS提供了在服务器之间使用标准IIOP协议进行事务传递的事务交互能力。JTS API由为企业中间件实现事务处理基础设施的供应商来使用。例如,一个EJB服务提供商可以使用JTS实现作为后台的事务管理器。
EJB架构不要求EJB容器支持JTS接口。EJB架构要求EJB容器支持JTA API和连接器API。
1.2 简单场景
本节描述几个阐述EJB
架构的分布式事务能力的场景。
1.2.1 更新多个数据库
EJB架构使得应用程序能够在一个事务中更新多个数据库中的数据。
在下图中,一个客户端调用企业bean X
,bean X
用两个数据库连接来更新数据库A
和B
中的数据。然后X
调用另外一个企业bean Y
。bean Y
更新数据库C
中的数据。EJB
服务器保证对数据库A
,B
和C
的更新要么全部提交,要么全部回滚。
图23 同时更新多个数据库
应用开发者不需要做任何事情来保证事务语义。在后台,EJB服务器获取数据库连接并将其作为事务的一部分。当事务提交时,EJB服务器和数据库系统执行两阶段提交协议以保证在三个数据库间的原子更新。
1.2.2 通过JMS Session发送或接收消息并更新多个数据库
EJB架构使应用程序可以发送消息到一个或多个JMS目的地,或从一个或多个JMS目的地接收消息,并且/或在一个事务内更新多个数据库。
在下图中,客户调用企业bean X
。bean X
给JMS
队列A
发送一个消息并使用连接JMS
提供商和数据库的连接来更新数据库B
中的数据。然后X
调用另一个企业bean Y
。bean Y
更新数据库C
中的数据。EJB
服务器保证在A
,B
和C
上的操作要么是全部提交,要么是全部回滚。
图24 向JMS队列中发送数据并更新多个数据库
应用开发者不必做任何事情来保证事务语义。企业bean X
和Y
使用标准JMS
和JDBC
的API
执行发送消息和更新数据库。在后台,EJB
服务器从JMS
提供商连接中获取session
,从数据库连接中获取连接,并把它们作为事务的一部分。当事务提交时,EJB
服务器和消息系统以及数据库系统共同执行两阶段提交协议以保证对这三个资源的原子更新。
在下图中,客户端发送消息到JMS
队列A
,这个队列由消息驱动bean X
使用。Bean X
用两个数据库B
和C
的连接来更新数据库。EJB
服务器保证从队列中取出JMS
消息、然后由bean X
接收,然后更新数据库B
和C
都是要么全部提交,要么全部回滚。
图25向用于消息驱动bean的消息队列中发送消息并更新多个数据库
1.2.3 通过多个EJB服务器更新数据库
EJB架构可以在同一个事务内更新不同地点的数据。
在下图中,客户端调用企业bean X
。bean X
更新数据库A
中的数据,然后调用另一个企业bean Y
,Y
安装在一个远程EJB
服务器上。Y
更新数据库B
的数据。EJB
架构可以在一个事务内更新数据库A
和B
中的数据。
图26 在一个事务内更新多个数据库
当X
调用Y
时,两个EJB
服务器协同把事务上下文从X
传递到Y
。事务传递对于应用层的代码来讲是透明的。
在事务提交时,两个EJB
服务器使用分布式的两阶段提交协议(如果有分布式的两阶段提交协议的话)来保证原子更新数据库。
1.2.4 客户端管理的事务分隔
Java客户端可以使用javax.tansaction.UserTransaction接口来现实地分隔事务边界。客户端程序通过依赖注入或通过在bean的EJBContext中或JNDI命名空间中查找的到javax.tansaction.UserTransaction。
使用显式事务分隔的客户端程序可以通过企业bean对位于多个EJB服务器上的多数据库执行原子更新。如下图所示:
图27 更新多个服务器上的多个数据库
应用开发者用begin
和commit
来分隔事务。如果企业bean X
和Y
配置成客户端事务(也就是说,它们的方法的事务属性是要求或者支持一个存在的事务上下文),EJB
服务器保证对数据库A
和B
的更新是客户端事务的一部分。
1.2.5 容器管理的事务分隔
只有客户端调用企业bean业务接口内的方法(或者home接口,或者企业bean的组件接口),容器拦截对这个方法的调用。拦截可以使容器能够通过事务属性声明式地控制事务分隔。(参见13.3.7事务属性描述)
例如,如果企业bean方法的事务属性是REQUIRED,那么容器的行为如下:如果客户端请求没有事务上下文且企业bean需要一个事务上下文,则容器自动初始化一个事务;如果客户端请求包含事务上下文,则容器将企业bean包含在客户端事务中(译者注:就是使用客户端的事务)。
下图解释了这种场景。一个非事务的客户端调用企业bean X
并且被调用的方法的事务属性是REQUIRED
(注:在本章中使用的TransactionAttribute
注释符的值参看事务属性。配置文件可以用于覆盖机制或注释符的替代方案,对于EJB2.1
和1.1
实体bean
则必须使用配置文件,因为它们不支持注释符)。由于客户端没有事务上下文,容器在调用X
上的方法之前开启一个新的事务。在事务上下文中执行Bean X
的方法。当X
调用其他企业bean
时(在这个例子中是Y
),由这个bena
执行的方法也包含在同一个事务中(遵循该bean
的事务属性)。
图28 从非事务客户端更新多个数据库
容器在X
返回时自动提交事务。
如果消息驱动bean的消息监听器方法配置是REQUIREDS事务属性,那么容器在转发消息之前自动启动一个新事务,因此也是在方法调用之前(这里使用的术语“容器”包括容器和消息提供商。当使用在
《
Java2 Enterprise Edition Connector Architecture, v1.5. http://java.sun.com/j2ee/connector
》中的协议时,它可以是消息提供商启动事务)。
JMS
要求在消息从队列中取出之前启动事务。
容器自动地征用和到达消息关联的资源管理器,并且所有的资源管理器都可以被带事务的消息监听器获得。
1.3 Bean提供者的责任
本节描述bean提供者眼中的事务和bean提供者的责任。
1.3.1 Bean管理的事务分隔与容器管理的事务分隔的对比
当设计企业bean时,开发者必须决定是在业务方法内用程序来分隔事务(bean管理的事务分隔)还是由容器基于定义(用注释符或配置文件)在业务方法上的事务属性来分隔事务(容器管理的事务分隔)。典型地,企业bean使用容器管理的事务分隔。这也是在不指定事务管理类型时的缺省值。
会话bean或消息驱动bean可以被设计成bean管理的事务分隔或者是容器管理的事务分隔。(但不能同时使用)。
EJB2.1或EJB1.1实体bean必须使用容器管理的事务分隔。它们不能被设计成bean管理的事务分隔。
不能为EJB3.0实体指定事务管理类型。在调用者的事务上下文中执行EJB3.0实体。参看本规范的文档“Java持久化API”来了解涉及EJB3.0的事务。
企业bean实例只能在企业bean方法的事务内获取资源管理器,由于在这个方法内才能获得事务上下文。
1.3.1.1 无事务执行
有些bean可能需要获取不支持外部事务协同的资源管理器。容器不能用同样的方式来为企业bean管理这些事务,但它能为企业bean管理那些支持外部事务协同的资源管理器的事务。
如果企业bean需要获得不支持外部事务协同的资源管理器,那么bean提供者应当把bean设计成是容器管理的事务分隔并且它的方法或所有方法的事务属性是NOT_SUPPORTED的。EJB架构不规范企业bean方法的事务语义。参看13.6.5了解容器如何来实现这种情况。
1.3.2 隔离级别
事务不仅仅是为了工作原子单元的完整性,也把工作单元相互隔离开来,这样系统就可以让多个工作单元共同执行。
隔离级别描述事务对资源管理器的获取与其他并发执行的事务对资源管理器的获取隔离的程度。
下面是管理隔离级别的指南:
l 管理隔离级别的API是资源管理器特有的。(因此,EJB架构不定义管理隔离级别的API)
l 如果企业bean使用多个资源管理器,bean提供者可以为每个资源管理器指定相同或不同的隔离级别。这就是说,例如如果一个企业bean在事务中获取多个资源管理器,对每一个资源的获取可能有不同的隔离级别。
l Bean提供者必须小心设置隔离级别。大多数资源管理器要求所有对资源管理器的获取需在一个事务内,且是相同的隔离级别。在事务中途改变隔离级别可能引起不可预料的行为,例如隐式的同步点(提交之前所有发生的改变)
l 对于使用bean管理事务分隔的会话bean和消息驱动bean,bean提供者可以通过使用资源管理器特定的API在企业bean的方法内手动指定隔离级别。例如bean提供者可以使用java.sql.Connection.setTransactionIsolation方法来设置合适数据库存取的隔离级别。
l 容器提供者应当保证提供合适的隔离级别来保证EJB2.1和2.0实体bean的数据一致性。典型地,就是要求高程度隔离的应用可以获得与重复读或可序列化隔离级别等价的隔离级别。
l 对于EJB2.1或更早的由容器管理持久化的实体bean,事务隔离由通过容器提供者工具生成的数据存取类来管理。这些工具必须保证由数据存取类管理的隔离级别不能与在事务内的资源管理器要求的隔离级别冲突。
l 如果多个企业bean在同一个事务内存取同一个资源管理器也要多加注意。必须避免要求的隔离级别间的冲突。
1.3.3 使用Bean管理事务分隔的企业bean
本节描述对使用bean管理事务分隔的企业bean的bean提供者的责任。
使用bean管理事务分隔的企业bean必须是一个会话bean或者是消息驱动bean。
在启动一个新事务前,必须完成前一个事务。
Bean提供者使用UserTransaction接口来分隔事务。在UserTransacton.begin和UserTransaction.commit方法之间所做的改变都会在一个事务内更新到资源管理器。当实例处于事务中时,实例不能使用资源管理器特定的事务分隔API(例如,不能调用java.sql.Connection接口或javax.jms.Session接口上的commit或rollback方法)。(注:然而,可以使用java持久化实体事务接口的API。)
有状态会话bean实例可以但不要求在业务方法返回之前提交事务。如果事务在业务方法最后没有被完成,那么容器在多个客户端之间保留事务和bean实例之间的关系直到事务最终被完成。
无状态会话bean实例必须在业务方法或超时回调方法返回之前提交事务。
消息驱动bean实例必须在消息监听器方法或超时回调方法返回之前提交事务。
下面的例子解释了一个业务方法内的事务涉及两个数据库连接。
@Stateless
@TransactionManagement(BEAN)
public class MySessionBean implements MySession {
@Resource javax.transaction.UserTransaction ut;
@Resource javax.sql.DataSource database1;
@Resource javax.sql.DataSource database2;
public void someMethod(...) {
java.sql.Connection con1;
java.sql.Connection con2;
java.sql.Statement stmt1;
java.sql.Statement stmt2;
// obtain con1 object and set it up for transactions
con1 = database1.getConnection();
stmt1 = con1.createStatement();
// obtain con2 object and set it up for transactions
con2 = database2.getConnection();
stmt2 = con2.createStatement();
//
// Now do a transaction that involves con1 and con2.
//
// start the transaction
ut.begin();
// Do some updates to both con1 and con2. The container
// automatically enlists con1 and con2 with the transaction.
stmt1.executeQuery(...);
stmt1.executeUpdate(...);
stmt2.executeQuery(...);
stmt2.executeUpdate(...);
stmt1.executeUpdate(...);
stmt2.executeUpdate(...);
// commit the transaction
ut.commit();
// release connections
stmt1.close();
stmt2.close();
con1.close();
con2.close();
}
...
}
下面的例子解释了一个业务方法内的事务既涉及数据库连接又涉及JMS连接。
@Stateless
@TransactionManagement(BEAN)
public class MySessionBean implements MySession {
@Resource javax.Transaction.UserTransaction ut;
@Resource javax.sql.DataSource database1;
@Resource javax.jms.QueueConnectionFactory qcf1;
@Resource javax.jms.Queue queue1;
public void someMethod(...) {
java.sql.Connection dcon;
java.sql.Statement stmt;
javax.jms.QueueConnection qcon;
javax.jms.QueueSession qsession;
javax.jms.QueueSender qsender;
javax.jms.Message message;
// obtain db conn object and set it up for transactions
dcon = database1.getConnection();
stmt = dcon.createStatement();
//obtainjmsconnobjectandsetupsessionfortransactions
qcon = qcf1.createQueueConnection();
qsession = qcon.createQueueSession(true,0);
qsender = qsession.createSender(queue1);
message = qsession.createTextMessage();
message.setText( some message
//
// Now do a transaction that involves the two connections.
//
// start the transaction
ut.begin();
// Do database updates and send message. The container
// automatically enlists dcon and qsession with the
// transaction.
stmt.executeQuery(...);
stmt.executeUpdate(...);
stmt.executeUpdate(...);
qsender.send(message);
// commit the transaction
ut.commit();
// release connections
stmt.close();
qsender.close();
qsession.close();
dcon.close();
qcon.close();
}
...
}
下面的例子解释一个有状态会话bean保留事务按照method1,method2,method3的调用顺序跨越三个客户端调用。(注意,在这里bean提供者必须使用pre-passivation回调方法来关闭连接和设置连接变量为null。)
@Stateful
@TransactionManagement(BEAN)
public class MySessionBean implements MySession {
@Resource javax.Transaction.UserTransaction ut;
@Resource javax.sql.DataSource database1;
@Resource javax.sql.DataSource database2;
java.sql.Connection con1;
java.sql.Connection con2;
public void method1(...) {
java.sql.Statement stmt;
// start a transaction
ut.begin();
// make some updates on con1
con1 = database1.getConnection();
stmt = con1.createStatement();
stmt.executeUpdate(...);
stmt.executeUpdate(...);
//
// The container retains the transaction associated with the
// instance to the next client call (which is method2(...)).
}
public void method2(...) {
java.sql.Statement stmt;
con2 = database2.getConnection();
stmt = con2.createStatement();
stmt.executeUpdate(...);
stmt.executeUpdate(...);
// The container retains the transaction associated with the
// instance to the next client call (which is method3(...)).
}
public void method3(...) {
java.sql.Statement stmt;
// make some more updates on con1 and con2
stmt = con1.createStatement();
stmt.executeUpdate(...);
stmt = con2.createStatement();
stmt.executeUpdate(...);
// commit the transaction
ut.commit();
// release connections
stmt.close();
con1.close();
con2.close();
}
...
}
企业bean也可以在每个业务方法内打开和关闭数据库连接(而不是保持连接直到事务结束)。在下面的例子中,如果客户端执行一串方法(method1,method2,method3),那么在同一个事务内执行method2中对数据库的所有更新,这个事务是在method1中启动,在method3中提交。
@Stateful
@TransactionManagement(BEAN)
public class MySessionBean implements MySession {
@Resource javax.Transaction.UserTransaction ut;
@Resource javax.sql.DataSource database1;
public void method1(...) {
// start a transaction
ut.begin();
}
public void method2(...) {
java.sql.Connection con;
java.sql.Statement stmt;
// open connection
con = database1.getConnection();
// make some updates on con
stmt = con.createStatement();
stmt.executeUpdate(...);
stmt.executeUpdate(...);
// close the connection
stmt.close();
con.close();
}
public void method3(...) {
// commit the transaction
ut.commit();
}
...
}
1.3.3.1 getRollbackOnly和setRollbackOnly方法
bean管理事务分隔的企业bean不需要使用EJBContext的getRollbackOnly和setRollbackOnly方法。原因如下:
l bean管理事务分隔的企业bean可以通过javax.transaction.UerTransaction的getStatus方法获得事务的状态。
l bean管理事务分隔的企业bean可以通过javax.transaction.UerTransaction的rollBack方法回滚事务。
1.3.4 使用容器管理事务分隔的企业bean
本节描述对使用容器管理事务分隔的bean提供者的要求。
企业bean的业务方法、消息监听器方法、业务方法拦截器方法、生命周期回调拦截器方法或超时回调方法不要使用资源管理器的特殊事务管理方法,它们会影响容器的事务边界分隔。例如,企业bean不可以使用java.sql.Connection接口的以下方法:commit,setAutoCommit和rollback;或javax.jms.Session接口的以下方法:commit和rollback。
企业bean的业务方法、消息监听器方法、业务方法拦截器方法、生命周期回调拦截器方法或超时回调方法不要使用或获取javax.transaction.UserTransaction接口。
下面的例子是一个使用容器管理事务分隔的企业bean业务方法。这个业务方法使用JDBC连接来更新两个数据库。容器提供由事务属性指定的事务分隔。(容器管理的事务分隔缺省的事务属性是REQUIRED。因此在这个例子中不需要显式地指定事务属性)
@Stateless public class MySessionBean implements MySession {
...
@TransactionAttribute(REQUIRED)
public void someMethod(...) {
java.sql.Connection con1;
java.sql.Connection con2;
java.sql.Statement stmt1;
java.sql.Statement stmt2;
// obtain con1 and con2 connection objects
con1 = ...;
con2 = ...;
stmt1 = con1.createStatement();
stmt2 = con2.createStatement();
//
// Perform some updates on con1 and con2. The container
// automatically enlists con1 and con2 with the container-
// managed transaction.
//
stmt1.executeQuery(...);
stmt1.executeUpdate(...);
stmt2.executeQuery(...);
stmt2.executeUpdate(...);
stmt1.executeUpdate(...);
stmt2.executeUpdate(...);
// release connections
con1.close();
con2.close();
}
...
}
1.3.4.1 Javax.ejb.SessionSynchornization接口
使用容器管理事务分隔的有状态会话bean可以有选择的实现javax.ejb.SessionSynchronization接口。在4.3.7章节中对SessionSynchronization作了描述。
1.3.4.2 Javax.ejb.EJBContext.setRollbackOnly方法
使用容器管理事务分隔的企业bean可以使用EJBContext的setRollbackOnly方法来标志事务不能提交。典型地,在抛出应用异常之前标志事务回滚来保护数据完整性,如果抛出未知的应用异常,则容器自动回滚事务。
例如,AccountTransfer
企业bean
从一个账户取钱然后存入另一个账户,如果取钱操作执行成功,但存钱操作失败,那么就可以标记这个事务回滚。
1.3.4.3 Javax.ejb.EJBContext.getRollbackOnly方法
使用容器管理事务分隔的企业bean可以使用EJBContext的getRollbackOnly方法来判断当前事务是否已经被标记为回滚。事务可以被企业bean自己标记为回滚,也可以被其他企业bean或者可以被事务处理框架内的其他组件(在EJB规范范围之外)标记为回滚。
1.3.5 在事务中使用JMS API
Bean提供者不应当在单个事务内使用JMS的请求/响应范例(发送消息然后同步接受这个消息)。因为JMS消息在事务提交前不会转发消息到目的地,所以在同一个事务内不会收到回复。
因为容器代表bean管理JMS会话的征用,所以createSession(Boolean transacted, int acknowledgeMode),createQueueSession(Boolean transacted, int acknowledgeMode)和createTopicSession(Boolean transacted, int acknowledgeMode)方法的参数将被忽略,但为acknowledgeMode设置值0.
Bean提供者不应在事务或在未指定事务上下文中使用JMS acknowledge模式。由容器来处理在未指定的事务上下文中的消息承认。13.6.5章节描述了容器能够用于带有未指定事务上下文的方法调用实现的一些技术。
1.3.6 Bean的事务管理类型规范
缺省情况下,如果没有指定事务管理类型,会话bean或消息驱动bean使用容器管理事务分隔。会话bean或消息驱动bean的bean提供者可以使用TransactionManagement注释符来声明会话bean或消息驱动bean使用bean管理的事务分隔还是容器管理的事务分隔。TransactionManagement注释符的值是CONTAINER或BEAN。TransactionManagement应用到企业bean的类上。
可选地,bean提供者也可以使用transaction-type配置元素来指定bean的事务管理类型。如果使用配置文件且使用bean管理的事务,那么必须显式指定bean的事务管理类型。
Bean的事务管理类型由bean提供者决定。应用组装者不允许使用配置文件来覆盖bean的事务管理类型,而无视事务类型是否已经被bean提供者显式地或缺省地指定。(参见19章来了解关于配置文件)
1.3.7 Bean方法上的事务属性规范
使用容器管理事务分隔的企业bean的bean提供者可以为企业bean的方法指定事务属性。缺省地,使用容器管理事务分隔的企业bean的方法的事务属性值是REQUIRED,在这种情况下,不需要显式指定事务属性。
事务属性是和下列方法关联的值:
l Bean的业务接口方法
l 消息驱动bean的消息监听器方法
l 超时回调方法
l 无状态会话bean的web 服务终端方法
l 对于用EJB2.1写的bean和早期的客户端视图,会话或实体bean的home或组件接口的方法
事务属性指定当客户端调用一个方法时容器必须如何为这个方法管理事务。
可以在以下方法中使用事务属性:
l 对于用于EJB3.0客户端视图API的会话bean,指定事务属性的方法对应于bean业务接口的方法,业务接口直接或间接父接口的方法,和超时回调方法(如果有)。
l 对于提供web服务客户端实体的无状态会话bean,事务属性用于bean的web服务终端方法,以及超时回调方法(如果有)。
l 对于消息驱动bean,事务属性用于消息监听器接口的方法和超时回调方法(如果有)。
l 对于用于EJB2.0的会话bean和早期的客户端视图,事务属性用于组件接口的方法和它的直接或间接父接口方法,除了javax.ejb.EJBObject或javax.ejb.EJBLocalObject接口的方法;以及超时回调方法(如果有)。不需要在会话bean的home接口的方法上指定事务属性。
l 对于EJB2.1或早期的实体bean,事务属性用于bean的组件接口和它的所有直接或间接父接口的方法,除了getEJBHome,getEJBLocalHome,getHandle,getPrimaryKey和isIdentical方法;以及home接口及其home接口的直接或间接父接口的方法,除了远程home接口的getEJBMetaData和getHomeHandle方法;已经超时回调方法(如果有)。(注意:对于EJB2.1和早期实体bean方法,如果事务属性不是Reuired,则必须使用配置文件)
如果没有为使用容器管理事务分隔的企业bean的方法指定TransactionAttribute注释符,那么事务属性的缺省值就是REQUIRED。事务属性规范的规则在13.3.7.1章节中定义。
Bean提供者可以使用配置文件替代注释符来指定事务属性(或者用于覆盖事务属性注释符)。在配置文件中指定的事务属性用于覆盖或补充用注释符指定的事务属性。如果在配置文件只能够没有指定事务属性,则就假定为用注释符指定事务属性,或者在没有指定注释符的情况下,则事务属性的值就是Required。
应用组装者可以用bean的配置文件来覆盖事务属性的值。部署员也可以在部署时覆盖事务属性的值。应当注意在覆盖应用的事务属性时,应用的事务结构本身是应用的语义(译者注:应该是由应用来控制的)。
企业bean为TransactionAttribute注释符定义了以下值:
l MANDATORY
l REQUIRED
l REQUIRES_NEW
l SUPPORTS
l NOT_SUPPORTED
l NEVER
与这些值对应的配置文件中的值是:
l Mandatory
l Required
l RequiresNew
l Supports
l NotSupported
l Never
在本章中,我们说TransactionAttribute
注释符值指的是事务属性。但是也要注意,也可以使用配置文件。
参见13.6.2章节来了解事务属性的值是如何影响由容器执行的事务管理。
对于消息驱动bean的消息监听器方法(或接口),只可以使用REQUIRED和NOT_SUPPORTED事务属性值。
对于企业bean的超时回调方法,只可以使用REQUIRED、REQUIRED_NEW和NOT_SUPPORTED事务属性值。
如果企业bean实现了java.ejb.SessionSynchronization接口,只可以使用REQUIRED、REQUIRED_NEW和MANDATORY。
上边的约束条件对保证只在一个事务中调用企业bean
是必需的。如果在非事务中调用bean
,那么容器就不能够发送事务同步调用。
对于使用EJB2.1容器管理持久化的实体bean,只可以使用Required,RequiresNew或Mandatory配置符事务属性值,这些值用于bean的组件接口和组件接口的直接或间接父接口的方法上,住了getEJBHome,getEJBLocalHome,getHandle,getPrimaryKey和isIdentical方法;以及bean的home接口和home接口的直接或间接父类的方法上,除了远程home接口的getEJBMetaData和getHomeHandle。
Bean
提供者和应用组装者必须注意什么时候和容器管理关系的迁移一起使用RequiresNew
事务属性。如果使用高的隔离级别,那么在新的事务上下文中进行容器管理的关系的迁移可能会引起死锁。
容器可以有选择的为使用容器管理持久化的EJB2.1实体bean的方法支持NotSupported,Supports和Never事务属性。但是,使用容器管理持久化的实体bean使用这些事务属性将是不可移植的。
容器可以有选择的为使用容器管理持久化的EJB2.1
实体bean
的方法支持NotSupported
,Supports
和Never
事务属性是因为使用这些事务模式可能需要使用带有非事务数据池的容器管理的持久化。通常情况下,bean
提供者和应用组装者应该避免为使用容器管理持久话的实体bean
的方法使用NotSupported
,Supports
和Never
事务属性,因为它在并发情况下可能导致不一致的结果,或导致持久化状态和关系的不一致更新或/
和部分更新。
1.3.7.1 使用事务属性元数据注释符的规范
下面是使用java元数据注释符的事务属性的规范。
TransactionAtrribute注释符用于指定事务属性。事务属性的值由枚举类TransactionAttributeType给出:
public enum TransactionAttributeType {
MANDATORY,
REQUIRED,
REQUIRES_NEW,
SUPPORTS,
NOT_SUPPORTED,
NEVER
}
事务属性可以指定在类上,类的业务方法上,或者都可以指定。
在bean的类上指定TransactionAttribute注释符意味着这个属性值应用到类的所有业务方法上。如果没有指定事务属性,那么缺省是REQUIRED。Bean类上指定其他事务属性的规范和TransactionAttribute(REQUIRED)一样。
事务属性可以指定在bean类的方法上来覆盖显式或隐式指定在bean类上的事务属性。
如果bean类有超类,则还应遵循下列规则:
l 在超类S上指定的事务属性应用到S的业务方法。如果在S上没有指定类级别的事务属性,那么等价于在S上指定TransactionAttribute(REQUIRED)。
l 可以在类S的业务方法M上指定事务属性来覆盖显式或隐式地在S上定义的事务属性。
l 如果类S的方法M重载了S超类上的业务方法,那么M的事务属性由上述的应用到S上的规则来决定。
举例:
@TransactionAttribute(SUPPORTS)
public class SomeClass {
public void aMethod () {...}
public void bMethod () {...}
...
}
@Stateless public class ABean extends SomeClass implements A {
public void aMethod () {...}
@TransactionAttribute(REQUIRES_NEW)
public void cMethod () {...}
...
}
假定aMehtod,bMethod,cMethod是接口A的方法,那么他们的事务属性分别是REQUIRED,SUPPORTS和REQUIRES_NEW。
1.3.7.2 配置文件中的事务属性规范
下面是在配置文件中的事务属性规范。(参见19.5章节了解配置文件完整的语法)
注意,即使没有使用注释符,为在章节13.3.7中列出的方法显式指定事务属性也不是必需的。如果在EJB3.0配置文件中没有指定一个方法的事务属性,则缺省的事务属性就是Required。
如果配置文件用于覆盖注释符,并且有些方法没有指定事务属性,那么在注释符(不管显式还是隐式)中指定的值将应用到那些方法上。
1.3.7.2.1 使用container-transaction元素
container-transaction元素用于为业务接口方法、home接口方法和消息监听器接口的方法以及web服务终端方法和超时回调方法定义事务属性。每个container-transaction元素由一到多个method元素和trans-attribute元素的列表组成。container-transaction元素指定所有列出的方法被分派所指定的事务属性值。要求在一个container-transaction元素内的所有方法必须是同一个企业bean的方法。
元素method使用ejb-name,method-name和method-params元素来声明一到多个方法。有三种合法的风格来组合method元素。
风格1:
EJBNAME
*
这种风格用于为风格2或风格3没有指定的方法的缺省事务属性值。一个企业bean中最多有一个使用风格1 method元素的container-transaction元素。
风格2:
EJBNAME
METHOD
这种风格用于为bean的业务接口、home接口、消息监听器、web服务终端和超时回调方法中的特定方法定义事务属性。如果多个方法有相同的名字,则这个风格指所有的这些同名的方法。对于给定的方法名,最多有一个使用风格2 method元素的container-transaction元素。如果同一个bean也有一个使用风格1元素的container-transaction元素,那么优先使用风格2的元素。
风格3:
EJBNAME
METHOD
PARAMETER_1
...
PARAMETER_N
这个风格用于在一堆同名的方法内指定一个特定的方法。如果同一个bean还有风格1和风格2元素,那么优先使用风格3的元素值。
可选元素method-intf元素用于区分在业务、组件、home接口和/或web服务终端间多次定义的有相同名字和标志符号的方法。但是,如果同名方法既是本地业务接口的方法有事本地组件接口的方法,那么在这个两个方法上应用相同的事务属性。同样地,如果同名方法既是远程业务接口的方法有事远程组件接口的方法,那么在这个两个方法上应用相同的事务属性。
下面是一个在配置文件中的事务属性规范的例子。EmployeeRecord企业bean的updatePhoneNumber方法分派的事务属性是Mandatory;所有其他的方法分派的事务属性是Required。企业bean AardvarkPayroll的所有方法分派的事务属性是RequiresNew。
...
...
EmployeeRecord
*
Required
EmployeeRecord
updatePhoneNumber
Mandatory
AardvarkPayroll
*
RequiresNew
1.4 应用装配员的责任
本节描述应用装配员视图及其责任。
没有机制让应用装配员影响使用bean管理事务分隔的企业bean。应用装配员不必为使用bean管理事务分隔的企业bean定义事务属性。
应用装配员可以使用配置文件事务属性机制来覆盖或改变使用容器管理事务分隔的企业bean的事务属性。
应用装配员应该小心地改变事务属性,因为由事务属性指定的行为本质上是应用语义的一部分。
1.5 部署员的责任
部署员可以在部署时覆盖和改变事务属性的值。
部署员应该小心地改变事务属性,因为由事务属性指定的行为本质上是应用语义的一部分。
兼容提示:对于使用EJB2.1
规范(以及早期的)写的应用,部署员有责任保证对于使用容器管理事务分隔的企业bean
,那些在配置文件中没有指定事务属性的方法都已经被分派了一个事务属性。
1.6 容器提供者的责任
本节定义了容器提供者的责任。
对会话或实体bean的业务方法接口(和/或home与组件接口)、web服务终端的方法和消息驱动bean的消息监听器方法的每一个调用都被容器拦截,并且通过容器获得企业bean使用的对资源管理器的连接。这个受管理的环境允许容器影响企业bean的事务管理。
这并没有暗指容器必须拦截所有企业bean
对资源的获取。典型地,容器只通过注册资源管理器连接工厂对象的特定容器实现拦截资源管理器工厂(例如JDBC
数据源)的JNDI
查找。资源管理连接工厂对象允许容器获得javax.transaction.xa.XAResource
接口并把它传入事务管理器。在设置完成后,企业bean
和资源管理器直接通讯而不通过容器。
1.6.1 Bean管理的事务分隔
本节定义了容器对使用bean管理事务分隔的企业bean的事务管理的责任。
注意,只有会话和消息驱动bean
可以用于bean
管理的事务分隔。
容器必须按下列方式管理客户对使用bean管理事务分隔的企业bean实例的调用。当客户通过企业bean的客户端视图接口调用它的业务方法时,容器挂起所有可能与和客户请求相关的事务。如果实例关联有事务(如果有状态会话bean实例在前面的业务方法中启动了这个事务),容器就使用者事务和方法执行关联。如果有拦截器方法和bean实例关联,则会在拦截器方法调用之前进行关联。
容器必须可以让企业bean的业务方法、消息监听器方法、拦截器方法或超时回调方法通过依赖注入和通过javax.ejb.EJBContext接口查找,以及使用JNDI命名上下文java:/UserTransaction获得javax.transaction.UserTransaction。当实例使用javax.transaction.UserTransaction来分隔事务时,容器必须在begin和commit或rollback之间征用实例使用的所有资源管理器。当实例提交事务时,容器负责事务提交的全局协调(通常容器依赖事务管理器,它是EJB服务器跨所有征用的资源管理器执行两阶段提交的一部分。如果事务只涉及一个资源管理器并且配置为可以使用连接共享,那么容器可以使用本地事务优化。)。
对于有状态bean,可以在没有提交或回滚事务的情况下完成启动事务的业务方法。在这种情况下,容器必须在多个客户端调用之间保留事务和实例的关联直到实例提交或回滚事务。当客户端调用下一个业务方法时,容器必须在事务上下文中调用这个业务方法(以及任何应用到这个bean上的拦截器方法)。
如果有状态会话bean实例在业务方法或拦截器方法内启动一个事务,那么它必须在业务方法(或所有的拦截器方法)返回之前提交事务。容器必须检测这种情况:一个事务在业务方法或拦截器方法中被启动但是没有完成。这种情况的处理如下:
l 记录为应用错误以警告系统管理员
l 回滚启动的事务
l 丢弃会话bean实例
l 抛出javax.ejb.EJBException(如果业务接口是一个继承java.rmi.Remote的远程业务接口,那么抛出java.rmi.RemoteException)。如果使用EJB2.1客户端视图,如果客户端是远程客户端则容器应当抛出java.rmi.RemoteException;如果客户端是本地客户端则抛出javax.ejb.EJBException。
如果消息驱动bean实例在消息监听器方法或拦截器方法内启动事务,那么它必须在消息监听器方法(或所有的拦截器方法)返回之前提交事务。容器必须检测这种情况:事务在消息监听器方法或拦截器方法内被启动但没有完成,并且按照以下方式处理:
l 记录为应用错误以警告系统管理员。
l 回滚启动的事务
l 丢弃消息驱动bean实例
如果会话bean或消息驱动bean实例在超时回调方法内启动事务,则它必须在超时回调方法返回之前提交事务。容器必须检测这种情况:事务在超时回调方法内被启动但没有完成,并按以下方式处理:
l 记录为应用错误以警告系统管理员。
l 回滚启动的事务
l 丢弃bean的实例
对使用bean管理的事务容器执行的动作总结在下表中。T1是一个和客户端请求关联的事务,T2是一个当前和实例关联的事务(也就是说,是一个由前一个业务方法启动但没有完成的事务)。
表12 对使用bean管理事务的bean方法容器的动作
客户端事务
|
当前和实例关联的事务
|
和方法关联的事务
|
None
|
None
|
None
|
T1
|
None
|
None
|
None
|
T2
|
T2
|
T1
|
T2
|
T2
|
对表内的每一个条目描述如下:
l 如果客户端请求没有关联事务而且实例也没有关联事务,或者如果bean是消息驱动bean,容器调用未指定事务上下文的实例。
l 如果客户端请求关联了事务T1,但是实例没有关联事务,那么容器挂起客户端关联的事务并且调用未指定事务上下文的方法。当方法(包括它关联的拦截器方法)完成后再次唤醒客户端事务T1。对于消息驱动bean或无状态bean的web服务终端方法调用来说,永远都不会发生这种情况。
l 如果客户端请求没有关联事务但实例已经关联了事务T2,那么容器容器调用管理事务T2的实例。对于无状态会话bean或消息驱动bean,永远都不会发生这种情况:只会发生在有状态会话bean上。
l 如果客户端关联了事务T1,但实例关联事务T2,容器挂起客户端事务然后调用使用T2事务上下文的实例方法。当方法(包括它的所有拦截器方法)完成后,容器重新使用客户端事务T1. 对于无状态会话bean或消息驱动bean,永远都不会发生这种情况:只会发生在有状态会话bean上。
容器必须允许企业bean实例在一个方法内按顺序执行几个事务。
在实例没有提交前一个事务时,如果实例使用javax.transaction.UserTransaction的方法begin启动事务,那么容器必须在begin方法内抛出javax.transaction.NotSupportedException。
如果使用bean管理事务分隔的bean实例调用javax.ejb.EJBContext的setRollbackOnly或getRollbackOnly方法,那么容器必须抛出java.lang.IllegalStateException。
1.6.2 为会话和实体bean使用的容器管理的事务分隔
容器有责任为声明为容器管理事务分隔的会话bean、使用bean管理持久化的实体bean和使用容器管理持久化的EJB2.1以及EJB1.1实体bean提供事务分隔。对于这些企业bean,容器必须按照在元数据注释符或配置文件中指定的事务属性来分隔事务。
以下的子章节定义了当通过企业bean业务接口(和/或home或组件接口)或者web服务终端的方法调用企业bean业务方法时,容器对企业bean业务方法的调用的管理责任。容器的责任依赖于事务属性的值。
1.6.2.1 NOT_SUPPORT
容器调用事务属性设置为NOT_SUPPORT但没有事务上下文的企业bean。
如果客户端调用有事务上下文,那么容器在调用企业bean的业务方法之前挂起和当前线程关联的事务上下文。容器当业务方法完成后,容器重新启动挂起的事务。挂起的事务不会传递资源管理器或在业务方法内调用的其他企业bean对象。
如果业务方法调用其他企业bean,容器不在调用中传递事务上下文。
参考13.6.5节更加详细的了解容器如何实现这种情况。
1.6.2.2 REQUIRED
容器必须使用有效的事务上下文调用事务属性为REQUIRED的企业bean方法。
如果客户端有事务上下文,则容器使用客户端的事务上下文调用企业bean的方法。
如果客户端没有事务上下文,容器在代理对企业bean业务方法调用之前自动启动一个新事务。容器自动通过带事务的业务方法征用所有的资源管理器。如果业务方法调用其他企业bean,容器在调用中传递事务上下文。当业务方法完成时,容器提交事务。容器在方法返回值被返回到客户端之前执行提交协议。
1.6.2.3 SUPPORTS
容器按以下方式调用事务属性是SUPPORTS的企业方法。
l 如果客户端调用带有事务上下文,那么容器执行和REQUIRED相同的步骤。
l 如果客户端调用没有事务上下文,容器执行和NOT_SUPPORTED相同的步骤。
必须小心SUPPORTS
事务属性。这是因为可能有两种不同的执行模型。只有那些可以在两种模式下都能执行的企业bean
才能使用SUPPORTS
事务属性。
1.6.2.4 REQUIRES_NEW
容器必须使用新的事务上下文来调用事务属性是REQUIRES_NEW的企业bean的方法。
如果调用企业bean方法的客户端没有事务上下文,容器在代理对企业bean方法调用之前自动启动一个新的事务。容器自动征集带事务的业务方法使用的资源管理器。如果业务方法调用其他企业bean,容器在调用中传递事务上下文。当业务方法完成时,容器提交事务。容器在方法返回之前执行提交协议。
如果客户端有事务上下文,那么容器在启动新事务之前挂起当前线程中的事务,然后调用业务方法。容器在业务方法和新事务完成之后重新启动挂起的事务。
1.6.2.5 MANDATORY
容器必须在客户端的事务上下文中调用事务属性是MANDATORY的业务方法。要求客户端有事务上下文。
l 如果客户端有事务上下文,那么容器执行和REQUIRED相同的步骤。
l 如果客户端没有事务上下文(如业务接口是继承java.rmi.Remote的远程业务接口,那么应javax.transaction.TransactionRequiredException抛到客户端)。如果使用EJB2.1客户端视图,如果是远程客户端则抛出javax.transaction.TransactionRequiredException,如果是本地客户端则抛出javax.ejb.TransactionRequiredLocalException。
1.6.2.6 NEVER
容器在非事务上下文中调用事务属性是NEVER的企业bean方法。客户端要求没有事务上下文。
l 如果客户端有事务上下文,容器抛出javax.ejb.EJBException(如果业务接口是继承java.rmi.Remote远程业务接口,则抛出java.rmi.RemoteException)。如果使用EJB2.1客户端视图,那么如果客户端是远程客户端则容器抛出java.rmi.RemoteException,如果是本地客户端则抛出javax.ejb.EJBException。
l 如果客户端没有事务上下文,那么容器执行和NOT_SUPPORTED一样的步骤。
1.6.2.7 事务属性总结
下表对容器传递到业务方法和业务方法使用的资源管理器的事务上下文的总结。T1是一个客户端请求带过来的事务,T2是容器初始化的事务。
表13 事务属性总结
事务属性
|
客户端事务
|
和业务方法关联的事务
|
和资源管理器关联的事务
|
NOT_SUPPORTED
|
None
|
None
|
None
|
T1
|
None
|
None
|
REQUIRED
|
None
|
T2
|
T2
|
T1
|
T1
|
T1
|
SUPPORTS
|
None
|
None
|
None
|
T1
|
T1
|
T1
|
REQUIRED_NEW
|
None
|
T2
|
T2
|
T1
|
T2
|
T2
|
MANDATORY
|
None
|
Error
|
N/A
|
T1
|
T1
|
T1
|
NEVER
|
None
|
None
|
None
|
T1
|
Error
|
N/A
|
如果企业bean的业务方法通过其他企业bean的业务接口或home和组件接口调用其他业务bean,那么在列“和业务方法关联的事务”中指明的事务将会作为客户端上下文的一部分被传递到目标企业bean。
1.6.2.8 方法setRollbackOnly的处理
容器必须处理按以下方式处理来自事务属性是REQUIRED, REQUIRES_NEW或MANDATORY业务方法对EJBContext.setRollbackOnly方法的调用:
l 容器必须保证事务从不被提交。典型地,容器向事务管理器发出标志事务回滚的指令。
l 如果容器在分派业务方法到实例之前立刻初始化事务(相反,事务继承自调用者),那么容器必须注意实例已经调用了setRollbackOnly方法。当业务方法调用完成时,容器必须回滚而不是提交事务。如果业务方法正常返回或有业务异常,那么容器必须在回滚执行之后将返回结果或应用异常返回到客户端。
如果从事务属性是SUPPORTS,NOT_SUPPORTED或NEVER的业务方法中调用EJBContext.setRollbackOnlyfangfa ,那么容器必须抛出java.lang.IllegalStateException。
1.6.2.9 方法getRollbackOnly的处理
容器必须处理从事务属性是REQUIRED,REQUIRES_NEW或MANDATORY业务方法中对EJBContext.getRollbackOnly的调用。
如果从事务属性是REQUIRED,REQUIRES_NEW或MANDATORY的业务方法中调用EJBContext.getRollbackOnlyfangfa ,那么容器必须抛出java.lang.IllegalStateException。
1.6.2.10 方法getUserTransaction的处理
如果使用容器管理事务分隔的企业bean实例调用EJBContext的getUserTransaction方法,那么容器必须抛出java.lang.IllegalStateException。
1.6.2.11 Javax.ejb.SessionSynchronization回调
如果会话bean实现了javax.ejb.SessionSynchronization接口,那么容器必须在实例上调用afterBegin,beforeCompletion和afterCompletion回调,这些调用作为事务提交协议的一部分。
容器在调用第一个业务方法之前调用afterBegin方法。
容器调用beforeCompletion给企业bean最后回滚事务的机会。实例可以通过调用EJBContext.setRollbackOnly来引起事务回滚。
容器在完成事务提交协议之后调用afterCompletion来通知企业bean实例完成事务。
1.6.3 对于消息驱动bean的容器管理事务分隔
容器有责任为声明为容器管理事务分割的消息驱动bean提供事务分隔。对于这些企业bean,容器必须根据注释符或配置文件的指定来分隔事务。(参见19章了解配置文件)
下面的章节描述容器对消息驱动bean的消息监听器方法调用的管理。容器根据事务属性的值来进行管理。
只有NOT_SUPPORTED和REQUIRED事务属性可以应用到消息驱动bean的消息监听器方法上。在消息驱动bean的消息监听器方法上使用其他事务属性没有意义,因为没有预先存在的客户端事务上下问(REQUIRES_NEW,SUPPORTS),并且没有客户端来处理异常(MANDATORY,NEVER)。
1.6.3.1 NOT_SUPPOTED
容器用不确定的事务上下文调用事务属性是NOT_SUPPORTED的消息驱动bean的消息监听器方法。
如果消息监听器方法调用其他企业bean,那么容器不传递事务上下文。
1.6.3.2 REQUIRED
容器必须使用有效的事务上下文调用事务属性是REQUIRED的消息驱动bean的消息监听器方法。由在事务内的消息监听器方法获得资源管理器被带着事务征用。如果消息监听器方法调用其他企业bean,容器传递这个事务上下文。当消息监听器方法完成时,容器提交事务。
消息系统可以在考虑可靠性的服务质量和消息出列的处理上不一致。
对与JMS要求如下:
事务必须在JMS
消息出列之前启动事务,也就是在消息驱动bean
的onMessage
方法被调用之前。和将要到达的消息关联的资源管理器被带着事务征用,以及在事务内有onMessage
方法获取所有的资源管理器。如果onMessage
方法调用其他企业bean
,容器传递事务上下文。当onMessage
完成时提交事务。如果onMessge
没有成功完成或是事务被回滚,那么应用消息重发语义。
1.6.3.3 方法setRollbackOnly的处理
容器必须按照以下方式处理在事务属性是REQUIRED的消息监听器方法中对EJBContext.setRollbackOnly方法的调用:
l 容器必须保证事务从不被提交。典型地,容器向事务管理器发出标志事务回滚的指令。
l 容器必须注意已经调用了setRollbackOnly方法的实例。当方法调用完成时,容器必须回滚事务。
如果在事务属性是NotSupported的消息监听器方法内调用EJBContext.setRollbackOnly,那么容器必须抛出并记录java.lang.IlleagalStateException。
1.6.3.4 方法getRollbackOnly的处理
容器必须处理在事务属性是REQUIRED的消息监听器方法内对EJBContext.getRollbackOnly方法的调用。
如果在事务属性是NotSupported的消息监听器方法内调用EJBContext.getRollbackOnly,那么容器必须抛出并记录java.lang.IlleagalStateException。
1.6.3.5 方法getUserTransaction的处理
如果使用容器管理事务分隔的消息驱动bean调用EJBContext的getUserTransaction方法,那么容器必须抛出并记录java.lang.IlleagalStateException。
1.6.4 本地事务优化
容器可以为元数据注释为或配置为资源管理器连接是共享的(参见16.7.1.3“在配置文件中声明资源连接器工厂”)企业bean使用本地事务优化。容器使用本地资源管理器优化对应用是透明的。
容器可以为由容器初始化的容器管理事务分隔的事务和由使用UserTransaction接口来进行bean管理事务分隔的bean初始化的事务使用优化。容器不能为来自其他容器的事务使用优化。
本地事务优化的方法在《java平台企业版(Java EE),v5.http://jcp.org/en/jsr/detail?id=244》和《java2企业连接器架构,v1.5,http://java.suan.com/j2ee/connector》中说明。
1.6.5 运行在“不明事务上下文”中的方法的处理
在EJB规范中的术语“不明事务上下文”指的是EJB架构不能完全定义企业bean方法执行的事务语义。
这包括以下情况:
l 事务属性是NOT_SUPPORTED,NEVER或SUPPORTS的使用容器管理事务分隔的企业bean的方法。
l 使用容器管理事务分隔的会话bean的PostConstruct,PreDestroy,PostActivate或PrePassivate回调方法。(注:参看第4章“会话bean组件协议”)
l 使用容器管理事务分隔的消息驱动bean的PostConstruct或PreDestroy回调方法。(参看第5章,“消息驱动bean组件协议”)
EJB规范没有描述容器如何管理具有不明事务上下文的方法的执行——事务语义留给容器实现。下面列出了容器可以选择的一些技术(这个列表没有包括所有可能的策略):
l 容器可以不使用事务上下文执行这个方法并获取后台资源管理器。
l 容器可以把对资源管理器的每一次调用都看作一个单事务(例如,容器可以将JDBC连接设置为自动提交模式)。
l 容器可以将对资源管理器的多个调用合并成到一个单事务中。
l 容器可以将对多个资源管理的多个调用合并到一个单事务中。
l 如果实例调用其他企业bean的方法,并且这些被调用的方法也指派为运行在不明事务上下文中,那么容器可以将来自多个实例的对资源管理器的调用合并到一个单事务中。
l 上边的任意组合。
由于企业bean不知道容器实现了哪一种技术,企业bean必须注意不要依赖与任何特定的容器行为。
当运行在未指定事务上下文中的方法执行中途失败时,可以保留从处于不明状态的方法获取的资源管理器。EJB架构没有定义在发生失败后应用应该如何恢复资源管理器的状态。
1.7 来自在同一个事务上下文中的多个客户端的存取
本节描述更加复杂的分布式事务场景,并指定了这个场景要求的容器的行为。
1.7.1 带实体对象的事务“钻石”场景
多个客户端可以在同一个事务内获取一个实体对象。例如,程序A可以启动一个事务,在事务上下文中调用程序B和程序C,然后提交事务。如果程序B和C获取同一个实体对象,事务拓扑创建成一个钻石。
图29 带实体对象的事务钻石场景
一个例子(没有实际意义),一个客户端程序试图在同一个事务内对两个不同的存储池执行两次购买。在每个存储池,程序处理客户端的购买请求并减少客户的银行账户。
对于一个EJB服务器的实现来说处理这种情况(在EJB服务器内程序B和C通过不同的网络路径获取实体对象)是困难的。这是因为许多EJB服务器将EJB容器实现为多进程的集合并运行在同一个或多个机器上。如果客户端B和C连接到不同的EJB容器进程,并且B和C需要获取处于相同事务中的同一个实体,那么问题就是容器如何能够让B和C看到处于同一事务中的实体的一致状态(钻石问题只应用到当B和C处于同一个事务的情况)。
上例解释了一个简单钻石问题。我们使用术语“钻石”指任何通过多个网络路径获取处于同一事务内的同一实体的分布式事务场景。
注意,在钻石场景中,客户端B和C按顺序获取实体对象。同时获取处于同一事务上下文中的实体对象将被认为是应用程序错误,将被容器以自己的方式处理。
注意,处理钻石问题不只存在于EJB
架构。这个问题存在于所有的分布式事务处理系统。
下面的子章节定义了在处理出现涉及实体对象的钻石分布式事务拓扑时各EJB角色的责任。
1.7.2 容器提供者的责任
这个章节定义了EJB容器的在涉及实体对象的钻石情况下的责任。
EJB规范要求容器支持本地钻石。在本地钻石中,组件A,B,C和D被配置在同一个EJB容器。
EJB规范不要求EJB容器支持分布式钻石。在分布式钻石中,多个客户端通过多个网络路径获取在同一个事务中的目标实体,并且客户端(程序B和C)是和目标实体对象不在同一个容器内的企业bean。
如果容器提供者选择不支持分布式钻石,并且如果容器能够检测到客户端调用将引起钻石,容器应当抛出javax.ejb.EJBException(或如果使用EJB2.1远程客户端实体则抛出java.rmi.RemoteException)。
1.7.3 Bean提供者的责任
这个章节定义了bean提供者的在涉及实体对象的钻石情况下的责任。
钻石情况对bean提供者是透明的——bean提供者不需要改变参与到钻石中的企业bean的代码。由容器实现钻石问题的解决方案对bean都是透明的,并且不改变bean的语义。
1.7.4 应用组装者和部署者的责任
这个章节定义了应用组装者和部署者在涉及实体对象的钻石情况下的责任。
应用组装者和部署者应该知道可能发生分布式钻石。一般情况下,应用组装者应当试着去避免创建不必要的分布式钻石。
1.7.5 涉及会话对象的事务钻石
当两个客户端获取同一个会话bean是合法时,使用会话bean的应用可能遭遇钻石情况。例如,程序A启动一个事务然后调用两个不同的会话对象。
图30 带会话bean的事务钻石场景
如果会话bean实例在同一个事务中跨多个方法调用缓存同一个数据项(例如,账户100的余额),那么程序很可能产生不正确的结果。
问题可以存在而不管两个会话对象是同一个或是不同的会话bean。如果在事务初始化器和缓存数据的会话对象间有中介对象,那么也可能存在问题(并且可能很难发现)。
对容器提供者没有要求,因为容器不可能检测到这个问题。
Ben提供者和应用组装者必须避免创建会引起在同一个事务内由多个会话对象缓存不一致数据的应用。