Spring框架其中的一个优点是它全面的事务支持,其好处有:
传统来说,J2EE有两个事务有两种选择: 全局和本地 .
全局事务由应用服务器管理,使用JTA。
其主要限制在于, 通常需要将JTA、JNDI同时使用,因为通常JTA的UserTransaction是通过JNDI获得的。
局部事务和资源相关的,比如和一个JDBC链接关联的事务。
其限制在于它们不能同事用于多个事务性资源。例如,使用JDBC连接事务管理的代码不能用于全局的JTA事务中。另外一个缺点是 局部事务趋向于入侵式编程模型。
Spring解决了这方面的问题,它让开发者能够使用在 任何环境下使用一致的编程模型。它同时提供声明式和编程式事务管理。 事务管理是多数使用者的首选,推荐使用!
通常,我们需要先定义一个DataSource,然后使用Spring的DataSourceTransactionManager,并传入指向DataSource的引用:
<bean id=
"dataSource"
class
=
"org.apache.commons.dbcp.BasicDataSource"
destroy-method=
"close"
>
<property name=
"driverClassName"
value=
"${jdbc.driverClassName}"
/>
<property name=
"url"
value=
"${jdbc.url}"
/>
<property name=
"username"
value=
"${jdbc.username}"
/>
<property name=
"password"
value=
"${jdbc.password}"
/>
</bean>
|
再定义一个 事务管理 类:
<!-- 事务管理 -->
<bean id=
"txManager"
class
=
"org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<property name=
"dataSource"
ref=
"dataSource"
/>
</bean>
|
Spring解决了这方面的问题,它让开发者能够使用在 任何环境下使用一致的编程模型。它同时提供声明式和编程式事务管理。 事务管理是多数使用者的首选,推荐使用!
Spring事务抽象的关键是 事务策略的概念 ,
主要通过org.springframework.transaction.PlatformTransactionManager接口定义
这里需要说一下 TransactionStatus对象,它代表一个新的或已经存在的事务.
另外,TransactionDefinition接口指定:
下面,做个解释一下,上面的几个特点:
隔离级别是指若干并发事务之间的隔离程度。
TransactionDefinition接口中定义了五个表示隔离级别的常量:
所谓事务传播行为是指, 如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
TransactionDefinition定义中包括了如下几个表示传播行为的常量:
上面的前6项,都是从EJB引入的。 他们共享相同的概念。 而PROPAGATION_NESTED是Spring 所特有的。以PROPAGATION_NESTED启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。
其类似于JDBC的SavePoint的概念,嵌套事务的子事务就是保存点的一个应用,一个事务中包括多个保存点,每一个嵌套子事务。 另外,外部事务的回滚也导致嵌套事务的回滚。
所谓事务超时,指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在TransactionDefinition中以int的值来表示超时时间,单位是秒。
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。 所谓事务性资源就是指那些被事务管理的资源,比如数据源、JMS资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在TransactionDefinition中以boolean类型来表示改事务是否只读。
在Spring 框架中事务管理的API最重要的有三个: TransactionDefinition、PlatformTransactionManager、TransactionStatus. 事务管理可以理解为: "按照给定的事务规则来执行提交或者回滚操作"!
"给定的事务规则"就是用TransactionDefinition表示的, "按照......来执行或回滚操作"便是用PlatformTransactionManager来表示,而TransactionStatus 用于表示一个运行着的事务的状态。打一个不恰当的比喻,TransactionDefinition与TransactionStatus的关系就像是程序和进程的关系.
编程式事务管理我们需要在代码中显式的调用beginTransaction()、commit() 、rollback()等事务管理相关的方法,这就是编程式事务管理。 通过Spring 提供的事务管理API,我们可以在代码中灵活的控制事务执行。而在底层,Spring仍然将事务操作委托给底层的持久化框架来执行。
小例子:
public
class
BankServiceImpl
implements
BankService {
private
BankDao bankDao;
private
TransactionDefinition txDefinition;
private
PlatformTransactionManager txManager;
public
boolean
transfer(Long fromId, Long toId,
double
amount) {
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
boolean
result =
false
;
try
{
result = bankDao.transfer(fromId,toId,amount);
txManager.commit(txStatus);
}
catch
(Exception e) {
result =
false
;
txManager.rollback(txStatus);
}
return
result;
}
}
|
配置文件:
<bean id=
"bankService"
class
=
"footmark.spring.core.tx.programmatic.origin.BankServiceImpl"
>
<property name=
"bankDao"
ref=
"bankDao"
/>
<property name=
"txManager"
ref=
"transactionManager"
/>
<property name=
"txDefinition"
>
<bean
class
=
"org.springframework.transaction.support.DefaultTransactionDefinition"
>
<property name=
"propagationBehaviorName"
value=
"PROPAGATION_REQUIRED"
/>
</bean>
</property>
</bean>
|
我们在类中增加了两个属性:
一个是 TransactionDefinition 类型的属性,它用于定义一个事务;
另一个是 PlatformTransactionManager 类型的属性,用于执行事务管理操作。
上面的方式很容易理解,但是有一个问题,就是事务处理的代码散落在各个类中,破坏了原有代码的条理性,我们可以在Spring使用模板回调模式
public
class
BankServiceImpl
implements
BankService{
private
BankDao bankDao;
private
TransactionTemplate transactionTemplate;
public
boolean
transfer(Long fromId,Long toId,
double
amount) {
return
(Boolean)transactionTemplate.execute(
new
TransactionCallback() {
public
Object doInTransaction(TransactionStatus status) {
Object result;
try
{
result=bankDao.transfer(formId,toId,amount);
}
catch
(Exception e) {
status.setRollbackOnly();
result=
false
;
}
return
result;
}
});
}
}
|
配置文件也有相应的修改:
<bean id=
"bankService"
class
=
"footmark.spring.core.tx.programmatic.template.BankServiceImpl"
>
<property name=
"bankDao"
ref=
"bankDao"
/>
<property name=
"transactionTemplate"
ref=
"transactionTemplate"
/>
</bean>
|
Spring的声明式事务管理在底层是建立在AOP的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行目标完成之后根据执行情况提交或者回滚事务。
声明式事务最大的优点在于 不需要通过编程的方式管理事务,这样就不需要在业务逻辑中参杂事务管理代码,只需要在配置文件中做相关的事务规则声明,便可将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正式AOP的用武之地。
声明式事务的大致流程如下图:
下面介绍几种常用的声明式事务策略:
<!-- 定义事务规则 -->
<bean id=
"transactionInterceptor"
class
=
"org.springframework.transaction.interceptor.TransactionInterceptor"
>
<!-- 指定事务管理器 -->
<property name=
"transactionManager"
ref=
"transactionManager"
/>
<!-- 定义事务规则 -->
<property name=
"transactionAttributes"
>
<props>
<!-- key 是方法名 ,值为事务属性-->
<prop key=
"transfer"
>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 组装target和advice -->
<bean id=
"bankService"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<property name=
"target"
ref=
"bankService"
/>
<property name=
"interceptorNames"
>
<list>
<idref bean=
"transactionInterceptor"
/>
</list>
</property>
</bean>
|
这里需要说明一下指定事务属性的取值规则:
传播行为[.隔离级别][. 只读属性][.超时属性][不影响提交的异常][.导致回滚的异常]
举两个例子:
<property name=
"*Service"
>
PROPAGATION_REQUIRED. ISOLATION_READ_COMMITTED. TIMEOUT_20.
+AbcException. +DefException. -HijException
</property>
|
上面的表达式主要针对方法名为Service结尾的方法,使用PROPAGATION_REQUIRED事务传播行为,事务的隔离级别是ISOLATION_READ_COMMITTED, 超时时间为20秒,当事务抛出AbcException或者DefException类型的异常,则仍然提交,当抛出HijException类型的异常时,必须回滚事务。这里没有指定readOnly,表示事务不是只读的。
上面的配置有个局限,即配置文件太多. 我们必须给每一个目标对象配置一个ProxyFactoryBean,加上目标对象本身,每一个业务类可能需要对应三个<bean/>配置,随着业务类的增多,配置文件会变得越来越庞大,所以Spring提供了TransactionProxyFacotyBean,用于将TransactionInterceptor和ProxyFactoryBean的配置合二为一。
<!-- transactionProxyFacotryBean方式 -->
<bean id=
"bkService"
class
=
"org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
>
<property name=
"target"
ref=
"bankServiceTarget"
/>
<property name=
"transactionManager"
ref=
"transactionManager"
/>
<property name=
"transactionAttributes"
>
<props>
<prop key=
"transfer"
>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
|
我们把这种配置方式称为 Spring 经典的声明式事务管理。
Spring2.x引入了<tx> 命名空间,结合使用<aop>命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于<aop>命名空间的切入点表达式支持,声明式事务变得更加强大.
<!-- tx命名空间方式 -->
<tx:advice id=
"bkAdvicetx"
transaction-manager=
"transactionManager"
>
<tx:attributes>
<tx:method name=
"transfer"
propagation=
"REQUIRED"
/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression=
"execution(* *.transfer(..))"
id=
"bkPointCut"
/>
<aop:advisor advice-ref=
"bkAdvice"
pointcut-ref=
"bkPointCut"
/>
</aop:config>
|
由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。
另外,如果配置的事务管理器 Bean 的名字取值为"transactionManager",
则我们可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为"transactionManager"。
Spring2.x还引入了基于Annotation的方式,具体主要涉及到@Transactional标注。
@Transactional可以作用于接口、接口方法、类以及类方法上。
当作用于类上时,该类的所有 public方法都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
@Transaction
(propagation=Propagation.REQUIRED)
public
boolean
transfer(Long fromId,Long toId,
double
amount){
return
bankDao.transfer(fromId,toId,amount);
}
|
Spring采用BeanPostProcessor来处理Bean中的标注,因此我们需要在配置文件中作如下声明来激活该后处理Bean,
<tx:annotation-driven transaction-manager=
"transactionManager"
/>
|
transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,否则将被忽略,也不会抛出任何异常。
基于 <tx> 命名空间和基于 @Transactional 的事务声明方式各有优缺点。
基于 <tx> 的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,
而基于 @Transactional 的方式必须在每一个需要使用事务的方法或者类上用 @Transactional 标注,尽管可能大多数事务的规则是一致的,但是对 @Transactional 而言,也无法重用,必须逐个指定。
另一方面,基于 @Transactional 的方式使用起来非常简单明了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。