基本概念和词汇:
n Transaction object (or Transaction component): 是一个参与在事务当中的组件。如:Ejb 组件、.Net组件、Corba组件等。
n Transaction manager: 负责管理Transaction objects的事务操作。就像乐队中的指挥一样。
n Resource:持久的数据源。如:数据库、消息队列等。
n Resource manager: Resource的管理者,负责管理所有持久数据的状态。Resource manager的一个例子就是数据库、消息队列或其他存储器的驱动,最流行的Resource manager的接口就是X/Open XA resource manager接口。很多数据库驱动都支持这种接口,XA 事实上已经是Resource manager的工业标准。
n ACID (atomicity, consistency, isolation, durability): 一个事务肯定会由数据库给予四种担保,他们是:原子性atomicity, 一致性consistency, 隔离性isolation, 持久性durability。
Transaction models. 事务一般有两种模型:扁平(flat)事务和嵌套(nested)事务。当前的Ejb规范只支持扁平事务而不支持嵌套事务。
n 扁平事务模型:就是将一系列操作视为一个操作。事务开始之后,程序可以做任意多的操作,可以是持久化的操作也可以不是。这种模型的特点是:如果事务能够正常结束则将所有操作持久化,否则所有操作都不会被持久化。
n 嵌套事务模型:将原子事务操作嵌套入其他事务操作中。一个小事务块回滚不会导致整个事务回滚。而大的事务块可以重试失败了的小事务块。你可以将嵌套事务模型想象为一个树状模型,最大的事务块是树根,小的事务块是树叶,叶结点回滚不会影响他的父结点。
Ejb中的Transaction。
n EJBean会很自然的被事务化,他是做那种要求严格的系统的一个很好的选择。
n 在ejb中,Transaction会被抽象化、底层化,你的代码不可能与Transaction manager或Resource manager交互。底层的事务控制被容器抽象化了,你的程序只要决定是否提交或回滚即可。
n 我们必须提供一些关键信息给容器,告诉他谁开始了事务、谁会提交或回滚事务、这些步骤何时发生。这被称作“划分事务边界”。有三种划分事务的方法:
1. 程序控制(Programmatic Transaction):事务都是由程序来控制,程序中显式的调用begin、commit、rollback等方法。
2. 容器控制(Declarative Transaction):容器截取程序控制权然后自动调用begin开始事务,然后将控制权交还给Bean,这时bean可以做任何逻辑业务操作,如果程序遇到问题可以给容器发送失败信号,在Bean完成操作之后会将控制权交还给容器,这时容器决定是否commit或rollback。你可以在Deploy descriptor配置文件加入以下这句话来指出使用上述那种控制方式:
<transaction-type>Container</transaction-type>
这句话指出由容器来做事务控制,如果将Container替换为Bean,则由程序来控制事务。
3. 客户端控制(Client initiated transaction):可以在Bean的客户端程序中控制事务,如:jsp、servlet、applet或其他ejb等。但是要注意还是要在配置文件中指出被调用的Bean是使用Programmatic Transaction还是Declarative Transaction。
n Transaction and Entity Bean
l 当你在transaction中调用Entity Bean时,容器首先调用Bean中的ejbLoad()方法从数据库中得到数据,实际就是从数据库得到数据锁并且确定数据是否与缓冲池中的一致。然后调用若干个业务逻辑方法,当事务被提交之后,ejbStore()方法被调用,他将所有更改过的数据写入数据库并且释放锁。
l 为什么Entity Bean只能使用Container-managed transaction?而Bean-managed transaction只能用于session bean或message-driven bean?这是因为:对于bean-managed transaction来说,你必须显式的调用begin()或commit()方法,在Entity bean中你可能会在ejbLoad()中开始事务,并且在ejbStore()中提交事务。但问题是,这两个方法只能由容器调用,所以bean不能确定这两个方法何时会被调用,也就是说你在ejbLoad()方法中开始了事务,或许这个事务永远也不会被结束。而session bean和message-driven bean中可以随意控制数据的操作,所以不存在这个问题。
l Entity bean不会在每一个方法被调用的时候去读写数据库,而是在一个事务之中。所以如果你的程序写的不好,每一个函数调用的时候都开始一个事务,那么每次调用这些函数的时候都会读写一次数据库!解决的办法是让多个函数处于一个事务当中,在配置文件中你可以配置这些。
n 程序控制、容器控制、客户端控制这三种事务控制方法我们到底应该选择哪一个呢?他们都各自有各自的优点:
l 程序控制:优点就是你可以在你的Bean中完全控制事务。比如,你可以在一个函数中运行多个小的事务;但是对于容器控制或客户端控制事务的Entity Bean中的函数却只能要么启动一个事务要么就一个都不能启动。
l 容器控制:优点就是很简单。可以减少代码量并且不用更改代码就可以调整事务。
l 客户端控制:举一个例子来说明这种控制方式的优点。假如一个没有客户端事务控制远程用户想要调用一个EJB,这个EJB有自己的事务控制。如果这个Bean的事务完成了,但是在操作结果返回给客户端的时候网络或者服务器瘫痪了,这是java就会给客户端抛出一个RMI的RemoteException来告诉客户端网络出故障了。但是这样的话客户端不会知道Bean的事物是否成功结束,这是客户端就要写代码去知道事务是否成功,而这些代码是非常繁琐并且很容易出错,因为网络的故障有可能客户端永远也连接不到服务器端了。客户端控制的事务的好处就是可以解决这个问题。但是,如果客户端远离服务器端的时候,要少用这种控制方式,因为如果距离太远,那么事务回滚的时候很有可能引起冲突。
n 容器控制的事务
l 我们必须提供一些事务属性(Transaction attribution)来告诉容器怎样控制事务。可以给每个Bean以不同的事务属性。这些属性定义在Deployment description中,而且你可以为Bean定义属性也可以为Bean中的某个函数定义属性,如果你对两者都定义了事务属性,则函数定义的属性优先。
l 对于Message-Driven Bean来说,建议最好使用容器控制的事务。
l 以下是事务属性值的解释:
u Required:必须使用事务,如果在这个Bean外面已经有了一个事务,那么就加入到这个事务当中;否则就开始一个新的事务。
u RequiresNew:无论外界是否有事务,都启动一个新的事务。
u Supports:如果外界已经有事务,则加入这个事务;否则,不会启动新的事务。
u Mandatory:规定Bean的函数被调用的时候必须已经启动了一个事务,否则会抛出TransactionRequiredException。
u NotSupported:Bean不会被事务控制。如果在调用Bean之前已经启动了一个事务,那么这个事务会被暂时挂起,直到Bean结束之后事务才会继续。
u Never:Bean不能进入事务,如果在一个事务中调用这个Bean的话会抛出RomoteException。
l 注意:不同类型的Bean不一定支持上述所有类型的属性值。如:Entiry Bean和带有SessionSyncronization的Stateful SessionBean不支持Never, NotSupported, Supports属性;而Message-driven Bean不支持Never, Supports, RequiresNew, Mandatory属性。
n Ejb程序控制的事务
l 程序控制的事务要比容器控制的事务更加灵活,但是比较繁琐。而且必须要使用Java Transaction API(JTA)。
l Object Management Group(OMG)组织定义了一个标准Object Transaction Service(OTS)在事务的底层做了很多工作,使得我们的代码就简单多了,并且不用关心底层的操作。
l Sun公司将OTS在Java语言中分成了两块:Java Transaction Service(JTS)和Java Transaction API(JTA)。其中JTS是给系统级的厂商使用的,它帮助我们做了很多底层的复杂的工作;而用户只要使用JTA就可以了。
l JTA分为两组接口:一组给X/Open XA resource manager使用;一组给用户使用。用户主要使用javax.transaction.UserTransaction接口。
l UserTransaction接口共有六个方法:being(), commit(), rollback(), getStatus(), setRollBackOnly(), setTransactionTimeout()。使用Context.getUserTransaction()可以得到UserTransaction。
l 如果想让程序控制事务则必须在Deployment descriptor中将<transaction-type>置为Bean
l 当我们在程序中启动一个事务时,最好在同一个方法中结束这个事务,否则长时间打开事务会严重消耗系统资源。
l 如果一个函数处在一个已经被其他Bean创建好了的事务中,那么这个函数怎样才能销毁这个事务呢?
u 在容器控制的事务中,不能通过抛出异常的手段来销毁事务,因为你抛出的异常有可能是你自己定义的异常,容器无法知道你自己定义的异常是否严重到要销毁事务的程度。所以因该使用Context.setRollBackOnly()方法来结束事务。
u 如果这个函数是一个普通的java object,则可以先找到JTA,然后调用setRollBackOnly()方法来结束事务。
u 在一个事务中如果有十个Bean,其中第二个Bean发现错误并且作了setRollBackOnly()操作,那么剩余的几个Bean就不能再做相同的操作,因为这样会大量的消耗资源。而应该得到当前事务的状态,根据这个状态来进行操作。得到事务状态的方法有两种:如果是容器管理的事务使用getRollbackOnly()方法;如果是程序控制的事务使用getStatus()方法。