事务Transactions
典型的企业应用存取存储在一个或多个数据库中的信息。因为这些信息对于业务操作来说非常重要,它必须是准确的,及时的并且可靠的。如果在同一时间允许多个程序更新同一条数据,那么数据完整性就会荡然无存。如果业务处理使业务数据被部分更新,那么数据完整性也无从谈起。为了避免这两种情况发生,软件事务保证了数据完整性。事务能处理多个程序的并发访问。在系统失败时,事务能确保数据在恢复之后能处于连续状态。
什么是事务?
为了完成一个业务事务,程序可能需要完成多个步骤。一个商业应用,比如,从一个帐号里转帐到另外一个账户上,步骤如下面伪码所示:
begin transaction
debit checking account
credit savings account
update history log
commit transaction
这三个步骤要么都完成,要么一个都没有完成。否则,数据完整性就不能保证。因为在一个事务里面的这些步骤,是一个统一的整体。一个事务总是被定义成不可分割的工作单元。
一个事务可以以两种方式结束:提交或者回滚。事务提交,数据更改就会被保存。如果事务中的 statement失败,则事务回滚,在 statement中的数据更改都会被撤销。在上面的伪码中,举个例子,如果在 credit步骤中,磁盘驱动器坏了,事务就会回滚并且撤销debit
statement所做的数据更改。尽管事务失败了,数据完整性仍能得到保障,因为帐号金额还是平衡的。
在前面的伪码中,begin和commit
statement标识了事务的边界。当设计一个企业bean时,你需要决定如何通过指定容器管理事务(CMT)或bean管理事务(BMT)来设定事务边界。
容器管理事务
在一个标识为容器管理事务的企业bean中,EJB容器设定了事务边界。在任何企业bean中你都可以用容器管理事务,比如session bean,message-driven bean。容器管理事务简化了企业bean的开发,因为企业bean代码不必明确标识事务边界,代码中不必包含begin 和 end transaction的statements。
缺省地,如果没有指定事务边界,则企业bean应用容器管理事务边界。
典型地,容器在企业 bean方法开始之前立刻就会启动事务。在方法退出之前事务会提交。每个方法都能关联到一个单一的事务。在一个方法里面,嵌套的或者多个事务都是不允许的。容器管理事务不需要所有方法都关联到事务中。在开发一个企业bean时,你需要指定这个bean的哪一个方法需要关联到事务中,通过设定事务属性就能指定。
采用了容器管理事务的企业bean绝对不能在用任何和容器管理事务边界冲突的其他事务管理方法。这些方法的例子有java.sql.Connection的commit
, setAutoCommit
, rollback
方法,或者javax.jms.Session的commit
and rollback
方法。如果你需要控制事务边界,你必须用应用管理事务边界。
应用CMT生命的企业bean,也绝不能用
javax.transaction.UserTransaction接口。
事务属性
事务属性控制着事务范围。图34-1表明为什么范围控制很重要。在这个图中,method-A启动了一个事务,然后调用Bean-2的method-B。当method-B执行的时候,它是在method-A启动的事务范围里面运行呢,还是启动一个新的事务?答案由method-B的事务属性决定。

Figure 34-1事务范围
事务属性可以具有以下几个值:
Required
RequiresNew
Mandatory
NotSupported
Supports
Never
Required
如果运行事务中的客户调用企业bean的方法,则方法运行于客户的事务中。如果客户没有关联到事务中,容器会在运行入方法之前启动一个新的事务。
Required属性是CMT中企业bean所有的事务属性中隐含的事务属性。一般除非你需要重载其他的事务属性,否则不需要指定Required属性。因为事务属性是声明性的,你可以随后很简单地更改它。
RequiresNew
如果客户端运行在事务里,并且调用了企业bean的方法,容器就会执行下列步骤:
1, 挂起客户端事务
2, 启动一个新的事务
3, 委派这个方法的调用
4, 在方法结束后重新恢复客户断事务
如果客户端没有关联到一个事务中,容器就会在运行这个方法之前的启动一个新的事务。
如果你想确保这个方法总是运行在一个新的事务中,你就可以用RequiresNew属性。
Mandatory
如果运行于事务中的客户调用了企业bean的方法,方法在客户的事务中执行。如果客户没有关联到事务中,容器就会抛出TransactionRequiredException。
如果企业bean方法必须用客户事务则采用Mandatory属性。
NotSupported
如果运行在一个事务里的客户调用了企业bean的方法,容器会在调用之前中止客户事务。在调用结束后,容器会恢复客户事务。
如果客户没有关联到一个事务中,容器不会在运行到方法前启动一个新的事务。
用NotSupported属性标识不需要事务的方法。因为事务会带来更高的性能支出,所以这个属性可以提高性能。
Supports
如果运行在事务中的客户调用了企业bean方法,这个方法运行于客户事务中。如果客户没有关联到事务中,容器不会在运行这个方法之前启动一个新的事务。
因为方法的事务行为是多变的,所以你要小心使用Supports属性。
Never
如果运行在事务中的方法调用了企业bean的方法,容器会抛出RemoteException。如果客户没有关联到事务,则容器不会在运行入方法之前启动一个新的事务。
事务属性概要
Table 34-1
概述了事务属性的作用,T1和T2都由容器控制。事务T1关联到客户,客户调用企业bean中的方法。在绝大多数场景,客户是另外一个企业bean。事务T2会在方法执行之前由容器启动。
在最后一列中,None意味着业务方法不会在容器控制的事务中运行。可是,在这些业务方法的数据库调用中,业务可以被DBMS的事务管理器控制。
Table 34-1 Transaction Attributes and Scope | ||
Transaction Attribute | Client's Transaction | Business Method's Transaction |
| None | T2 |
T1 | T1 | |
| None | T2 |
T1 | T2 | |
| None | error |
T1 | T1 | |
| None | None |
T1 | None | |
| None | None |
T1 | T1 | |
| None | None |
T1 | Error |
Setting Transaction Attributes
事务属性由注解于企业bean的javax.ejb.TransactionAttribute注解
指定,被设定为javax.ejb.TransactionAttributeType常量的某一个值。
如果你用@TransactionAttribute注解企业bean类,则指定的TransactionAttributeType会被应用到这个企业bean的所有方法上。而在一个方法上的注解只在这一个方法上有效。如果一个注解同时在类和方法上,则方法上的注解覆盖类上的注解。
TransactionAttributeType常量封装了前面小节描述的事务属性。
- Required:
TransactionAttributeType.REQUIRED
- RequiresNew:
TransactionAttributeType.REQUIRES_NEW
- Mandatory:
TransactionAttributeType.MANDATORY
- NotSupported:
TransactionAttributeType.NOT_SUPPORTED
- Supports:
TransactionAttributeType.SUPPORTS
- Never:
TransactionAttributeType.NEVER
以下代码片段示范了如何使用 @TransactionAttribute
注解:
@TransactionAttribute(NOT_SUPPORTED)
@Stateful
public class TransactionBean implements Transaction {
...
@TransactionAttribute(REQUIRES_NEW)
public void firstMethod() {...}
@TransactionAttribute(REQUIRED)
public void secondMethod() {...}
public void thirdMethod() {...}
public void fourthMethod() {...}
}
在这个例子中,TransactionBean类的事务属性被设定为
NotSupported。firstMethod为设定为
RequiresNew,secondMethod被设定为
Required。因为在方法上的@TransactionAttribute覆盖了类上的@TransactionAttribute,调用firstMethod会创建一个新的事务,调用secondMethod会在当前事务中进行或创建一个新的事务。thirdMethod和fourthMethod不会发生在事务中。
在CMT中回滚事务
在CMT中,有2种方法回滚事务。第一种,系统异常抛出,容器自动回滚事务;第二种调用EJBContext
接口的setRollbackOnly方法,bean方法就会通知容器回滚事务。如果bean抛出业务异常,除非调用setRollbackOnly否则容器不会自动回滚事务。
同步Session Bean实例变量
可选的SessionSynchronization接口,允许有状态session bean实例接受事务同步通知。例如,你可以用数据库中的响应值同步企业bean实例变量。容器调用SessionSynchronization的方法afterBegin,beforeCompletion和afterCompletion,在事务的主要阶段。
afterBegin方法通知企业bean实例新事务开始。容器在调用业务方法之前立即调用afterBegin方法。
容器在业务方法结束之后,但事务提交之前调用beforeCompletion方法。beforeCompletion方法是在事务提交之前回滚事务的最后机会(通过调用setRollbackOnly的方式)。
afterCompletion方法指明事务结束了。它有个boolean的返回值,分别标识事务提交(true)和回滚(false)。
不允许在CMT中出现的方法
你不能调用任何可能和容器设定的事务边界相冲突的方法。禁止的方法列表如下:
java.sql.Connection
的commit
,setAutoCommit
, 和rollback
方法
javax.ejb.EJBContext
的getUserTransaction
method of方法javax.transaction.UserTransaction
的所有方法
可是,你可以在应用管理事务(AMT)中用这些方法设定事务边界。
Bean管理事务Bean-Managed Transactions
在Bean管理事务边界中,session bean或MDB中的代码明确地标明了事务边界。尽管CMT的企业bean只需要较少的代码量,但它们有一个限制:当方法执行时,要么被关联到一个事务,要么就不能。如果这个限制使你的bean的编码变难的话,你就需要考虑使用容器管理事务了。
下面的伪码图示了Bean管理事务能使你得到的好处。通过检查变量条件,伪码在业务方法中决定是否启动或停止不同的事务。
begin transaction
...
update table-a
...
if (condition-x)
commit transaction
else if (condition-y)
update table-b
commit transaction
else
rollback transaction
begin transaction
update table-c
commit transaction
当编写容器管理事务的session bean 或MDB时,你需要决定是否采用JDBC或JTA事务,下面小节将讨论这两种事务类型。
JTA Transactions
JTA是Java Transaction API的简称。这个API使你用事务管理器实现独立的规则划定事务边界。应用服务器用Java Transaction Service (JTS)实现了事务管理器。但你的代码不能直接调用JTS的方法。你的代码调用JTA的方法,而由JTA调用底层的JTS。
JTA事务由Java EE事务管理器控制。你可能想用JTA事务来更新多个厂商提供的不同的数据库。一个确定的DBMS的事务管理器可能不能和不同的数据库合作。可是,Java EE事务管理器有一个限制:它不支持嵌套的事务。换句话说,在一个事务结束前是不能启动另一个事务的。
为了指定JTA事务,你需要调用javax.transaction.UserTransaction 接口的begin
, commit
, 和 rollback方法。
没有提交事务就返回
在容器管理事务的无状态session bean中,业务方法在返回之前必须提交或回滚事务。可是,有状态session bean没有这个约束。
应用JTA事务的有状态session bean,bean实例和事务直接的关联要在多个客户调用直接保持。即便客户调用的每个业务方法打开并关闭了数据库连接,这种关联还是要被保持到这个实例结束事务。
在JDBC事务的有状态session bean中,JDBC连接在多个调用之间保持了bean实例和事务。如果连接关闭,这种关联也就不存在了。
在Bean管理事务中不允许的方法
在Bean管理事务中不要调用EJBContext接口的getRollbackOnly和setRollbackOnly方法。这些方法只能在CMT中使用。对于Bean管理事务,应该调用UserTransaction
接口的getStatus和rollback方法。
事务超时
在CMT中,你可以通过设定domain.xml文件中的
超时时间属性来控制事务超时间隔,domain.xml在你的应用服务器的安装目录中的config目录下。例如:你可以像下面这样设定5秒超时:
timeout-in-seconds=5
这样设定好之后,如果事务在5秒中没有完成,则EJB容器就会把它回滚。
当应用服务器刚被安装时,超时值被设定为0:
timeout-in-seconds=0
如果超时时间设定为0,则事务不会超时。
只有在CMT事务中,超时属性才有效。对于由bean管理的JTA事务,你可以调用UserTransaction接口的setTransactionTimeout方法。
更新多个数据库
Java EE 事务管理器除了控制容器管理JDBC事务,还控制所有企业bean事务。Java EE 事务管理器允许在一个事务中更新多个数据库。下面的图显示了两个在同一事务中更新多个数据库的场景。
在图34-2中,客户调用Bean-A中的业务方法。这个方法启动了事务,更新数据X,更新数据库Y,并且调用Bean-B的业务方法。第二个业务方法更新数据库Z,并且把控制权返回到要提交事务的Bean-A中。所有的这3个数据库更新发生在同一个事务中。
java.sql.Connection或javax.transaction.UserTransaction接口
在web组件中标识事务。这两个接口和在bean管理事务中使用的接口是一样的。