文章目录
  1. 1. 概述
  2. 2. Spring VS EJB
  3. 3. Spring的声明式事务管理
    1. 3.1. 基于tx命名空间的声明式事务管理
    2. 3.2. 基于@Transactional的声明式事务管理

概述


Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是 AOP 的用武之地。Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。

声明式事务管理曾经是 EJB 引以为傲的一个亮点,如今 Spring 让 POJO 在事务管理方面也拥有了和 EJB 一样的待遇,让开发人员在 EJB 容器之外也用上了强大的声明式事务管理功能,这主要得益于 Spring 依赖注入容器和 Spring AOP 的支持。依赖注入容器为声明式事务管理提供了基础设施,使得 Bean 对于 Spring 框架而言是可管理的;而 Spring AOP 则是声明式事务管理的直接实现者。

和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

Spring的声明式事务管理是基于AOP的,而AOP是通过动态代理实现的。动态代理的一个重要特征是:它是针对接口的,所以如果dao或者service要通过动态代理让Spring接管事务,就必须在dao或者service抽象一个接口。常见的形式比如:

1
2
3
4
5
接口类:IUserService
实现类:UserService
或者
接口类:UserService
实现类:UserServiceImpl

当然如果没有这样的接口,那么Spring会使用CGLib来解决问题,但这不是Spring推荐的方式。

Spring VS EJB


EJB CMT和Spring声明式事务管理之间是有相似的,但也有很多不同。其基本方法是相似的:都可以指定事务到单独的方法、如果需要可以在事务上下文调用setRollbackOnly()方法。不同之处在于:

  • Spring声明式事务管理可以在任何环境下使用,只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事务机制一起工作。而EJB CMT要绑定在JTA上。
  • Spring可以使声明式事务管理应用到普通Java对象
  • Spring允许你通过AOP定制事务行为。例如,如果需要,你可以在事务回滚中插入定制的行为。你也可以增加任意的通知,就象事务通知一样。使用 EJB CMT,除了使用setRollbackOnly(),你没有办法能够影响容器的事务管理。
  • Spring提供声明式回滚规则:EJB没有对应的特性。回滚可以声明式控制,不仅仅是编程式的。

Spring的声明式事务管理

基于tx命名空间的声明式事务管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- 配置Spring的事务处理 -->
<!-- 创建事务管理器-->
<bean id="txManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置AOP,Spring是通过AOP来进行事务管理的 -->
<aop:config>
<!-- 设置pointCut表示哪些方法要加入事务处理 -->
<!-- 以下的事务是声明在DAO中,但是通常都会在Service来处理多个业务对象逻辑的关系,注入删除,更新等,-->
<!-- 此时如果在执行了一个步骤之后抛出异常,就会导致数据不完整,所以事务不应该在DAO层处理 -->
<!-- 而应该在service,这也就是Spring所提供的一个非常方便的工具,声明式事务 -->
<aop:pointcut id="allMethods"
expression="execution(* org.konghao.cms.service.*.*(..))" />

<!-- 通过advisor来确定具体要加入事务控制的方法 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="allMethods" />
</aop:config>
<!-- 配置哪些方法要加入事务控制 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 让所有的方法都加入事务管理,为了提高效率,可以把一些查询之类的方法设置为只读的事务 -->
<tx:method name="*" propagation="REQUIRED" read-only="true"/>
<!-- 以下方法都是可能设计修改的方法,就无法设置为只读 -->
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="clear*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

基于@Transactional的声明式事务管理


除了基于tx命名空间的事务配置方式,Spring还引入了基于 Annotation 的方式,具体主要涉及 @Transactional 标注。@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

1
2
3
4
@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId, double amount) {
return bankDao.transfer(fromId, toId, amount);
}

Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,因此我们需要在配置文件中作如下声明来激活该后处理 Bean:

1
<tx:annotation-driven transaction-manager="transactionManager"/>

虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

基于 命名空间和基于 @Transactional 的事务声明方式各有优缺点。基于 的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,而基于 @Transactional 的方式必须在每一个需要使用事务的方法或者类上用 @Transactional 标注,尽管可能大多数事务的规则是一致的,但是对 @Transactional 而言,也无法重用,必须逐个指定。另一方面,基于 @Transactional 的方式使用起来非常简单明了,没有学习成本。

文章目录
  1. 1. 概述
  2. 2. Spring VS EJB
  3. 3. Spring的声明式事务管理
    1. 3.1. 基于tx命名空间的声明式事务管理
    2. 3.2. 基于@Transactional的声明式事务管理