总结(2)是针对EJB3的事务和安全中的容器管理事务(CMT),BMT及安全部分放在总结(3)中。
完全是自己学习的心得,还请前辈们指点。
EJB3的事务与安全
EJB3的是事务也符合ACID,即原子性、一致性、隔离型、持久性。这些特性与数据库事务一致,需要强调的是一致性,在事务开始前,系统是处于一种遵守业务规则和约束的一致状态下,那么在事务提交或回滚之后,系统也必须维持这种一致性状态。在事务进行过程中不必处于不一致性状态,事务在这里就像一个沙箱(sand-box)。
在EJB中,事务也具有隔离级别的控制,但一般不用通过EJB容器来控制,而是在数据库资源这一级别来进行控制。
要知道在EJB容器中,代码层面的所有操作最终都是转化为两级的数据库操作,比如锁定和解锁数据库中的某行或某张表。事务日志来反映事务的变化,开始事务日志代表事务的开始,应用日志代表以提交该事务来结束,相反放弃日志就代表回滚事务而结束。
资源管理器(Resource Manager)的概念是管理特定某种资源的事务的组件。这个概念不仅包括关系数据库系统,也可以使消息服务器或其他业务系统。
如果只涉及一种单一资源的事务就称为local transaction本地事务,相反大多数企业应用都是需要涉及多种不同的资源。这时候就需要一种类型的组件来管理事务中多种资源,这个组件称为事务管理器(Transaction Manager),它在多个管理各自资源事务的资源管理之间进行协调和控制。
分布式的事务管理是通过一种称为两段式提交(two-phase commit)的机制来完成。现在最为流行的分布式事务协议是XA协议(XA Protocol),JavaEE就是通过该协议来完成分布式应用中的事务管理。
事务管理
JavaEE中事务使用JTA(Java Transaction API,是建立在Java Transaction Service之上的服务),即javax.tranaction.UserTransaction接口,容器在后台会自动管理大多数事务细节,EJB开发者只需控制开始和停止事务、建立事务边界(transaction boundary)以及是否提交/回滚业务即可。
在EJB3中提供了两种具体的事务方式,即容器管理的事务(Container-managed transaction,CMT)和bean管理的事务(Bean-managed transaction,BMT)。其中前者是使用声明式地或通过部署描述符来管理事务;后者需要以显式编码的方式来管理。但需要注意的是在EJB3中,只有SessionBean和MessageDrivenBean才支持CMT和BMT,JPA中并不直接依赖CMT或BMT。但是当然,在任何JavaEE容器中都可透明得插入CMT或BMT的事务环境。
EJB3中管理事务最简单灵活的方式是采用容器管理事务,即CMT。
容器管理的事务,顾名思义,容器来做“事务开始、事务提交或回滚”。容器在调用方法前开始JTA事务,接着会调用业务方法,最后根据调用中发生的情况去决定是提交事务还是回滚事务。使用CMT事务管理,只需关注@TransactionManagement和@TransactionAttribute注解,同时通过EJBContext的方法来回滚事务。
1.@TransactionMangement注解用于向容器标识该bean的事务管理是使用CMT还是BMT,是通过该标签中的value属性来指明TransactionManagementType的枚举值。
2.@TransactionAttribute注解,尽管由容器为我们来管理事务的各种细节,但是仍然需要通过该注解来告知容器如何去自动管理。
CMT对事务的管理可以在包装bean的方法时开始,也可以从调用者的事务中进行join开始。
TransactionAttributeType有几个枚举值:
(1) REQUIRED:如果调用者没有事务,容器则创建新事务;如果调用者具有事务,容器则连接join调用者事务。
(2) REQUIRED_NEW:如果调用者没有事务,容器就创建新事务;如果调用者有事务,容器暂停原来事务,并创建新的事务。
(3) SUPPORTS:如果调用者没有事务,容器就不创建事务;反之则join并使用调用者的事务。
(4) MANDATORY:如果调用者没有事务,容器抛出javax.ejb.EJBTransactionRequiredException异常;反之则连接并使用调用者的事务。
(5) NOT_SUPPORTED:如果调用者没有事务,容器便不使用事务调用方法;反之则暂停调用者事务以non-transactional的方式调用方法。
(6) NEVER:如果调用者没有事务,容器直接调用方法;反正抛出javax.ejb.EJBException异常。
a. 对于第1种情况,适合的场景是从non-transactional的Web层调用bean方法,如果方法调用出现异常,那么容器不仅会rollback整个事务,同时也会向调用者抛出javax.transaction.RollbackException异常,以通知调用者事务已被回滚了。
b. 第2种情况中,始终需要容器去创建新的事务,对于调用者原有的事物,容器就会暂停它,直到方法调用返回为止就恢复原有事务。这样的结果就是方法调用所创建的新事务无论成功与否,都不会对调用者原有的事务造成影响。
c. SUPPORT选项表示本质上容器将会按照调用者的事务情况来处理,通常的场景是用于一些只读型的操作,比如检索数据记录等。
d. MANDATORY表示对容器对事务的强制性要求,如果调用者存在事务,容器就join并使用调用者事务;但如果调用者没有事务环境,就会抛出EJBTransactionRequiredException异常。这种选项适合于如果方法调用失败进行回滚时也保证调用者环境的失败。
e. 对于NOT_SUPPORT,容器处理的本质是要求不能在事务环境下调用bean方法。如果调用者存在事务,容器会先暂停它,然后开始方法调用,在返回后恢复调用者的事务。
f. NEVER表明在CMT中,容器不能允许从事务性环境中调用bean方法,否则会抛出EJBException异常。
对于前面这六种TransactionAttributeType,MDB并不是全部都支持,它支持REQUIRED和NOT_SUPPORT两种。这一点与MDB的特性有关:客户端无法直接调用MDB,所以事务属性中的SUPPORT、REQUIRED_NEW和MANDATORY就没有意义。
CMT事务管理的真正机制
很重要的一点:CMT方法要求容器回滚事务并不是立即进行的,而是向容器设置回滚标识,在事务结束的时候,容器检查该标志,如果不需回滚则提交该事务,如果需要回滚则进行。
通过EJBContext或其子类:SessionContext和MessageDrivenContext的setRollbackOnly()方法,将回滚标志位置为true,当方法调用结束时,容器检查该状态以判断是否提交事务或是回滚事务。
需要注意的一点是,本质上,使用EJBContext进行设置标志位时,它是作为底层事务的一个上层抽象代理。所以使用时,必须保证有底层事务存在,也就是说必须在REQUIRED,REQUIRED_NEW,MADATORY这三种事务属性下使用!因为这三种事务属性都可以使容器保证底层事务的存在,即无论方法调用者是否存在事务环境,容器都会创建新事务。
与setRollbackOnly()方法对应的是getRollbackOnly()方法,该方法返回当前EJBContext中回滚标志位的状态。该方法在业务处理中十分有用,考虑这样一个场景:
在进行一段非常长的资源密集型操作前如果前事务的前部分已经失败,那就是在一个注定要回滚的事务上付出很多代价。所以在这类操作之前应该先check一下rollbackOnly是否已经置为true。
此外,处理该标志位状态的代码经常分布在业务逻辑的catch块中,如果catch了某种exception,则一边记录进log,一边通过set方法置状态位。在EJB3中,可以通过@ApplicationException注解使得“捕获异常转化为事务回滚”变成透明的机制。
@AplicationException注解
@javax.ejb.ApplicationException该注解可以用来控制事务性输出,将要抛出的异常类型在定义时加上该标签,同时设置该标签的rollback属性,即@ApplicationException(rollback=true/false)。应用异常(ApplicationException)是希望调用者或客户端处理的,一般认为除了java.rmi.RemoteException和java.lang.RuntimeException之外都是应用异常。而从上述两种异常继承出的子类异常都被认为是系统异常,这种异常不会传递给调用者或客户端而是wrapped在EJBException中。
添加此注解的异常无论是checked exception还是unchecked exception(比如运行时异常),都会被认为是应用异常(application exception),从而传递给方法调用者或客户端。
默认情况下,所有checked exception或所有被注解为应用异常的checked/unchecked exception,都不会以CMT的方式回滚,如果把rollback属性设置为true,则会通知容器失败时进行事务回滚,并且是在把应用异常传递给调用者或客户端之前进行的。
如果有未预料的unchecked exception,比如ArrayOutOfBoundsException或NullPointerException,容器仍然会回滚CMT事务,但是在这种情况下,容器会认为bean处在了一种inconsistent的情况下,会进而销毁bean实例,这是十分消耗资源的,所以you should never delibrately use system exceptions.
CMT使开发人员不必关心EJB事务中的大部分细节,当然同时CMT能提供对事务的控制级别也更少,这一点Bean管理的事务(BMT)更适合。