Advantages of the Spring Framework’s transaction support model
传统的Java EE的开发者对事务管理有两种选择:全局事务管理和局部事务管理(global or local transactions),但这两者都有巨大的缺陷。
全局事务(Global Transactions)
全局事务使你能够在跨多个事务资源上进行事务管理,典型的例子是关系数据库与消息队列。应用服务器通常通过JTA来管理全局事务。然而JTA是一个笨重的API(部分原因是因为它的异常模型)。而且,一个JTA的用户事务(userTranscation)通常需要通过JNDI来查找资源,这意味着为了使用JTA,必须使用JNDI. 很明显,使用全局事务会限制代码的重用性,因为JTA通常只在应用服务器中使用。
先前,最好的使用全局事务的方法是通过EJB CMT( Container Managed Transaction): CMT是一种声明式事务管理(区别于编程式事务管理)。EJB CMT使得事务不需要依赖于JNDI来查找,虽然使用EJB本身就需要使用JNDI. 它去除了大部分用来控制事务的java代码,但不是全部。CMT的一个明显的缺点是绑定了JTA和一个应用服务器环境。而且,也只能在那些在用EJBs或者在事务EJB门面之后实现的商业逻辑中使用。
优点:支持多个事务性资源间相互工作(关系数据库与消息队列)
缺点:
1. 要使用JTA,JTA比较笨重
2. JTA依赖JNDI来寻找资源,因此就需要同时使用JTA与JNDI
3. JTA通常在应用服务器环境下使用,因此JTA限制了代码的重用性
局部事务(Local Transactions)
局部事务是资源特有的(resource - specific),例如一个事务与JDBC链接相关联。局部事务更容易使用,但有明显不足: 它们不能在多个事务资源间工作。例如:用一个JDBC链接来管理事务的方法不能用于全局的JTA事务管理。因为应用服务器没有参与事务管理,不能保证访问多个资源时的正确性。(值得注意的是:大部分应用只是用一个事务资源。)另一个缺点是局部事务会侵入到程序模型中,是侵入式的。
优点:易用
缺点:
1. 不能支持多个事务性资源间的相互工作。
2. 通常局部事务是编程式的事务管理,对程序有入侵性。
Spring Framework’s consistent programming model
Spring 解决了全局和局部事务管理的缺点。它使得应用程序开发者能够在任何环境下使用固定不变的编程模型。你只需要写一次代码,就可以使用不同环境不同事务管理策略。Spring框架同时提供了声明式和编程式两种事务管理方式。大部分用户喜欢声明式的事务管理,它也是在大部分情况下地推荐方式。
采用编程式事务管理,开发者需要使用Spring提供的事务抽象,它能够在任意底层事务架构上运行。使用声明式事务管理时,开发者只需要写很少甚至不需要编写与事务管理相关的代码。因此它不依赖spring事务框架的API,和其他任何事务的API.
理解Spring框架提供的事务抽象
Spring事务抽象的关键是事务策略的概念(notion of a transaction strategy ). 事务策略是通过org.springframework.transaction.PlatformTransactionManager接口定义的。
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
它是主要的服务提供接口(service provider interface SPI). 因为PlatformTransactionManager是一个接口因此很容易模拟或者桩化。也不需要绑定lookup查找策略,如:JNDI. PlatformTransactionManager的实现可以像IoC容器中的其他对象(bean)一样被定义。Spring框架对事务的抽象即使在使用JTA时也同样有意义。事务代码比起直接使用JTA更加容易测试。
Spring捕获的异常是RunTimeException类型的异常。
getTransaction(..)方法根据TransactionDefinition参数返回一个TransactionStatus对象。返回的TransactionStatus可能是一个新的事务,也可能是一个已经存在在调度堆栈中(call stack)并且匹配的事务。也就是说,TransactionStatus是与线程执行相关联的。
TransactionDefinition接口,用于描述事务包含以下内容
事务隔离级别Isolation
read uncommitted : 可能出现“脏读(dirty read)”,“不可重复读(non-repeatable read)”,“幻读(phantom read)”等。因为可以读未提交的数据,一旦事务回滚,就可能读到错误的值。
read committed : 可以阻止“脏读”,可能出现“不可重复读”和“幻读”。这个级别只能读已经提交的事务的值。
repeatable read : 可以阻止“脏读”和“不可重复读”的出现,但“幻读”还是可能出现。在这个级别中,智能读已经提交的事务的值,并且一个事务读了这个值之后,其他事务不能对其进行修改。
serializable: 所有事务按序执行。可以避免“脏读”,“不可重复读”,以及”幻读“。
通常情况下使用的是read committed这个隔离级别。
事务传播Propagation
通常情况下,所有的代码都在同一个事务范围内执行。然而,你也可以选择在一个事务上下文中执行另一个事务级方法时产生的行为。如:代码继续在现存的事务中执行;或者现存的事务能够被挂起并重新创建一个事务。Spring提供了与EJB CMT相似的事务传播选项
PROPAGATION_REQUIRED: 支持当前事务,如果不存在则创建一个新的事务。默认设置。
PROPAGATION_SUPPORTS: 支持当前事务,如果不存在则按照非事务的方式执行。对于事务管理者来说,这种方式与完全不用事务的方式有略微区别,因为它定义了一个事务同步可能会应用的范围。
PROPAGATION_MANDATORY:支持当前事务,如果不存在当前事务则抛出异常。
PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果存在当前事务则挂起。该方式总是定义自己的事务同步范围。如果已经存在的同步会被挂起在合适的时候恢复。如果在方法A中调用了方法B,方法B的传播方式是PROPAGATION_REQUIRES_NEW,当B执行完成事务提交之后,方法A失败回滚,那么方法B不会回滚。当方法B失败,B回滚,抛出的异常被A捕获,方法A的事务仍然可能提交。
PROPAGATION_NOT_SUPPORTED: 不支持当前事务,总是以非事务的方式执行。事务同步在这种情况下是不能使用的。已经存在的同步会被挂起。
PROPAGATION_NEVER : 不支持当前事务,如果存在事务则抛出异常。
PROPAGATION_NESTED: 如果存在当前事务就在NESTED Transaction中执行。她与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW是另起一个事务,将会与父事务相互独立。Nested事务与父事务相依,它的提交要等和他的父事务一起提交。父事务回滚它也要回滚。
Timeout:定义事务的超时时间并且根据底层的事务结构自动回滚。
Read-only status: 当你的代码只读数据,不修改数据时,可以使用read-only事务。read-only事务在某些情况下是一种有用的优化方式,如当你使用hibernate时,可以避免dirty checking。
理解Spring框架的声明式事务管理(Understanding the Spring Framework’s declarative transaction implementation)
定义一个transactionManager来进行事务管理,并管理的datasource 或者sessionFactory。
定义一个transaction Advice来告诉transactionManager如何给每一个事务级方法设定事务管理的方式,如:传播性,read-only 还是 read-write等。
将事务管理以切面的方式加到具体的方法上。因此配置一个AOP,定义切面来指定具体的方法,将切面与adviser关联起来。
定义一个transaction manager
<bean id="txManager"
class=“org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
定义一个transaction advice
<tx:advice id="txAdvice" transaction-manager="txManager"]]>
<!-- the transactional semantics... --]]>
<tx:attributes>
<!-- all methods starting with 'get' are read-only --]]>
<tx:method name="get*" read-only="true”/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*”/>
</tx:attributes>
</tx:advice>
定义一个AOP,来确定在那些方法上加事务管理
<!-- ensure that the above transactional advice runs for any execution of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
这些配置具体怎么用呢?
这些配置会在bean FooService定义的对象周围加上一个事务代理。这个事务代理通过事务advice配置,因此当某一个方法被这个代理调用时,根据与方法相关联的事务配置,事务被启动、挂起或者标记为read-only等。
编程式事务管理:利用TransactionTemplate
在代码中显示的使用TransactionTemplate,并实现一个TransactionCallBack的接口(一般使用匿名内部类)实现了这个事务的上下文环境中我们需要执行的业务逻辑。