本教程将深刻讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务。经过对本教程的学习,您将可以理解 Spring 事务管理的本质,并灵活运用之。程序员
本教程假定您已经掌握了 Java 基础知识,并对 Spring 有必定了解。您还须要具有基本的事务管理的知识,好比:事务的定义,隔离级别的概念,等等。本文将直接使用这些概念而不作详细解释。另外,您最好掌握数据库的基础知识,虽然这不是必须。spring
要试验这份教程中的工具和示例,硬件配置需求为:至少带有 512MB 内存(推荐 1GB)的系统。须要安装如下软件:数据库
事务管理对于企业应用而言相当重要。它保证了用户的每一次操做都是可靠的,即使出现了异常的访问状况,也不至于破坏后台数据的完整性。就像银行的自助取款机,一般都能正常为客户服务,可是也不免遇到操做过程当中机器忽然出故障的状况,此时,事务就必须确保出故障前对帐户的操做不生效,就像用户刚才彻底没有使用过取款机同样,以保证用户和银行的利益都不受损失。express
在 Spring 中,事务是经过 TransactionDefinition 接口来定义的。该接口包含与事务属性有关的方法。具体如清单1所示:编程
清单1. TransactionDefinition 接口中定义的主要方法并发
1框架 2工具 3性能 4学习 5 6 |
|
也许你会奇怪,为何接口只提供了获取属性的方法,而没有提供相关设置属性的方法。其实道理很简单,事务属性的设置彻底是程序员控制的,所以程序员能够自定义任何设置属性的方法,并且保存属性的字段也没有任何要求。惟一的要求的是,Spring 进行事务操做的时候,经过调用以上接口提供的方法必须可以返回事务相关的属性取值。
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
所谓事务的传播行为是指,若是在开始当前事务以前,一个事务上下文已经存在,此时有若干选项能够指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了以下几个表示传播行为的常量:
这里须要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(若是存在外部事务的话),此时,内嵌事务并非一个独立的事务,它依赖于外部事务的存在,只有经过外部的事务提交,才能引发内部事务的提交,嵌套的子事务不能单独提交。若是熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中能够包括多个保存点,每个嵌套子事务。另外,外部事务的回滚也会致使嵌套子事务的回滚。
所谓事务超时,就是指一个事务所容许执行的最长时间,若是超过该时间限制但事务尚未完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
事务的只读属性是指,对事务性资源进行只读操做或者是读写操做。所谓事务性资源就是指那些被事务管理的资源,好比数据源、 JMS 资源,以及自定义的事务性资源等等。若是肯定只对事务性资源进行只读操做,那么咱们能够将事务标志为只读的,以提升事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
一般状况下,若是在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。若是没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这一般也是大多数开发者但愿的处理方式,也是 EJB 中的默认处理方式。可是,咱们能够根据须要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务。
Spring 框架中,涉及到事务管理的 API 大约有100个左右,其中最重要的有三个:TransactionDefinition、PlatformTransactionManager、TransactionStatus。所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操做”。“给定的事务规则”就是用 TransactionDefinition 表示的,“按照……来执行提交或者回滚操做”即是用 PlatformTransactionManager 来表示,而 TransactionStatus 用于表示一个运行着的事务的状态。打一个不恰当的比喻,TransactionDefinition 与 TransactionStatus 的关系就像程序和进程的关系。
该接口在前面已经介绍过,它用于定义一个事务。它包含了事务的静态属性,好比:事务传播行为、超时时间等等。Spring 为咱们提供了一个默认的实现类:DefaultTransactionDefinition,该类适用于大多数状况。若是该类不能知足需求,能够经过实现 TransactionDefinition 接口来实现本身的事务定义。
PlatformTransactionManager 用于执行具体的事务操做。接口定义如清单2所示:
清单2. PlatformTransactionManager 接口中定义的主要方法
1 2 3 4 5 6 |
|
根据底层所使用的不一样的持久化 API 或框架,PlatformTransactionManager 的主要实现类大体以下:
若是咱们使用JTA进行事务管理,咱们能够经过 JNDI 和 Spring 的 JtaTransactionManager 来获取一个容器管理的 DataSource。JtaTransactionManager 不须要知道 DataSource 和其余特定的资源,由于它将使用容器提供的全局事务管理。而对于其余事务管理器,好比DataSourceTransactionManager,在定义时须要提供底层的数据源做为其属性,也就是 DataSource。与 HibernateTransactionManager 对应的是 SessionFactory,与 JpaTransactionManager 对应的是 EntityManagerFactory 等等。
PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。返回的TransactionStatus 对象可能表明一个新的或已经存在的事务(若是在当前调用堆栈有一个符合条件的事务)。TransactionStatus 接口提供了一个简单的控制事务执行和查询事务状态的方法。该接口定义如清单3所示:
清单3. TransactionStatus 接口中定义的主要方法
1 2 3 4 5 |
|
在 Spring 出现之前,编程式事务管理对基于 POJO 的应用来讲是惟一选择。用过 Hibernate 的人都知道,咱们须要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。经过 Spring 提供的事务管理 API,咱们能够在代码中灵活控制事务的执行。在底层,Spring 仍然将事务操做委托给底层的持久化框架来执行。
根据PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三个核心接口,咱们彻底能够经过编程的方式来进行事务管理。示例代码如清单4所示:
清单4. 基于底层 API 的事务管理示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
相应的配置文件如清单5所示:
清单5. 基于底层API的事务管理示例配置文件
1 2 3 4 5 6 7 8 9 |
|
如上所示,咱们在类中增长了两个属性:一个是 TransactionDefinition 类型的属性,它用于定义一个事务;另外一个是 PlatformTransactionManager 类型的属性,用于执行事务管理操做。
若是方法须要实施事务管理,咱们首先须要在方法开始执行前启动一个事务,调用PlatformTransactionManager.getTransaction(...) 方法即可启动一个事务。建立并启动了事务以后,即可以开始编写业务逻辑代码,而后在适当的地方执行事务的提交或者回滚。
经过前面的示例能够发现,这种事务管理方式很容易理解,但使人头疼的是,事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,而且每个业务方法都包含了相似的启动事务、提交/回滚事务的样板代码。幸亏,Spring 也意识到了这些,并提供了简化的方法,这就是 Spring 在数据访问层很是常见的模板回调模式。如清单6所示:
清单6. 基于 TransactionTemplate 的事务管理示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
相应的XML配置以下:
清单 7. 基于 TransactionTemplate 的事务管理示例配置文件
1 2 3 4 5 |
|
TransactionTemplate 的 execute() 方法有一个 TransactionCallback 类型的参数,该接口中定义了一个 doInTransaction() 方法,一般咱们以匿名内部类的方式实现 TransactionCallback 接口,并在其 doInTransaction() 方法中书写业务逻辑代码。这里可使用默认的事务提交和回滚规则,这样在业务代码中就不须要显式调用任何事务管理的 API。doInTransaction() 方法有一个TransactionStatus 类型的参数,咱们能够在方法的任何位置调用该参数的 setRollbackOnly() 方法将事务标识为回滚的,以执行事务回滚。
根据默认规则,若是在执行回调方法的过程当中抛出了未检查异常,或者显式调用了TransacationStatus.setRollbackOnly() 方法,则回滚事务;若是事务执行完成或者抛出了 checked 类型的异常,则提交事务。
TransactionCallback 接口有一个子接口 TransactionCallbackWithoutResult,该接口中定义了一个 doInTransactionWithoutResult() 方法,TransactionCallbackWithoutResult 接口主要用于事务过程当中不须要返回值的状况。固然,对于不须要返回值的状况,咱们仍然可使用 TransactionCallback 接口,并在方法中返回任意值便可。
Spring 的声明式事务管理在底层是创建在 AOP 的基础之上的。其本质是对方法先后进行拦截,而后在目标方法开始以前建立或者加入一个事务,在执行完目标方法以后根据执行状况提交或者回滚事务。
声明式事务最大的优势就是不须要经过编程的方式管理事务,这样就不须要在业务逻辑代码中掺琐事务管理的代码,只需在配置文件中作相关的事务规则声明(或经过等价的基于标注的方式),即可以将事务规则应用到业务逻辑中。由于事务管理自己就是一个典型的横切逻辑,正是 AOP 的用武之地。Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。
声明式事务管理曾经是 EJB 引觉得傲的一个亮点,现在 Spring 让 POJO 在事务管理方面也拥有了和 EJB 同样的待遇,让开发人员在 EJB 容器以外也用上了强大的声明式事务管理功能,这主要得益于 Spring 依赖注入容器和 Spring AOP 的支持。依赖注入容器为声明式事务管理提供了基础设施,使得 Bean 对于 Spring 框架而言是可管理的;而 Spring AOP 则是声明式事务管理的直接实现者,这一点经过清单8能够看出来。
一般状况下,笔者强烈建议在开发中使用声明式事务,不只由于其简单,更主要是由于这样使得纯业务代码不被污染,极大方便后期的代码维护。
和编程式事务相比,声明式事务惟一不足地方是,后者的最细粒度只能做用到方法级别,没法作到像编程式事务那样能够做用到代码块级别。可是即使有这样的需求,也存在不少变通的方法,好比,能够将须要进行事务管理的代码块独立为方法等等。
下面就来看看 Spring 为咱们提供的声明式事务管理功能。
最初,Spring 提供了 TransactionInterceptor 类来实施声明式事务管理功能。先看清单8的配置文件:
清单 8. 基于 TransactionInterceptor 的事务管理示例配置文件
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 |
|
首先,咱们配置了一个 TransactionInterceptor 来定义相关的事务规则,他有两个主要的属性:一个是 transactionManager,用来指定一个事务管理器,并将具体事务相关的操做委托给它;另外一个是 Properties 类型的 transactionAttributes 属性,它主要用来定义事务规则,该属性的每个键值对中,键指定的是方法名,方法名可使用通配符,而值就表示相应方法的所应用的事务属性。
指定事务属性的取值有较复杂的规则,这在 Spring 中算得上是一件让人头疼的事。具体的书写规则以下:
1 |
|
如下是两个示例:
1 2 3 4 |
|
以上表达式表示,针对全部方法名以 Service 结尾的方法,使用 PROPAGATION_REQUIRED 事务传播行为,事务的隔离级别是 ISOLATION_READ_COMMITTED,超时时间为20秒,当事务抛出 AbcException 或者 DefException 类型的异常,则仍然提交,当抛出 HijException 类型的异常时必须回滚事务。这里没有指定"readOnly",表示事务不是只读的。
1 |
|
以上表达式表示,针对全部方法名为 test 的方法,使用 PROPAGATION_REQUIRED 事务传播行为,而且该事务是只读的。除此以外,其余的属性均使用默认值。好比,隔离级别和超时时间使用底层事务性资源的默认值,而且当发生未检查异常,则回滚事务,发生已检查异常则仍提交事务。
配置好了 TransactionInterceptor,咱们还须要配置一个 ProxyFactoryBean 来组装 target 和advice。这也是典型的 Spring AOP 的作法。经过 ProxyFactoryBean 生成的代理类就是织入了事务管理逻辑后的目标类。至此,声明式事务管理就算是实现了。咱们没有对业务代码进行任何操做,全部设置均在配置文件中完成,这就是声明式事务的最大优势。
前面的声明式事务虽然好,可是却存在一个很是恼人的问题:配置文件太多。咱们必须针对每个目标对象配置一个 ProxyFactoryBean;另外,虽然能够经过父子 Bean 的方式来复用 TransactionInterceptor 的配置,可是实际的复用概率也不高;这样,加上目标对象自己,每个业务类可能须要对应三个 <bean/> 配置,随着业务类的增多,配置文件将会变得愈来愈庞大,管理配置文件又成了问题。
为了缓解这个问题,Spring 为咱们提供了 TransactionProxyFactoryBean,用于将TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一。如清单9所示:
清单9. 基于 TransactionProxyFactoryBean 的事务管理示例配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
如此一来,配置文件与先前相比简化了不少。咱们把这种配置方式称为 Spring 经典的声明式事务管理。相信在早期使用 Spring 的开发人员对这种配置声明式事务的方式必定很是熟悉。
可是,显式为每个业务类配置一个 TransactionProxyFactoryBean 的作法将使得代码显得过于刻板,为此咱们可使用自动建立代理的方式来将其简化,使用自动建立代理是纯 AOP 知识,请读者参考相关文档,不在此赘述。
前面两种声明式事务配置方式奠基了 Spring 声明式事务管理的基石。在此基础上,Spring 2.x 引入了 <tx> 命名空间,结合使用 <aop> 命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于 <aop> 命名空间的切点表达式支持,声明式事务也变得更增强大。
如清单10所示:
清单10. 基于 <tx> 的事务管理示例配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
若是默认的事务属性就能知足要求,那么代码简化为如清单 11 所示:
清单 11. 简化后的基于 <tx> 的事务管理示例配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
因为使用了切点表达式,咱们就不须要针对每个业务类建立一个代理对象了。另外,若是配置的事务管理器 Bean 的名字取值为“transactionManager”,则咱们能够省略 <tx:advice> 的 transaction-manager 属性,由于该属性的默认值即为“transactionManager”。
除了基于命名空间的事务配置方式,Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及@Transactional 标注。@Transactional 能够做用于接口、接口方法、类以及类方法上。看成用于类上时,该类的全部 public 方法将都具备该类型的事务属性,同时,咱们也能够在方法级别使用该标注来覆盖类级别的定义。如清单12所示:
清单12. 基于 @Transactional 的事务管理示例配置文件
1 2 3 4 |
|
Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,所以咱们须要在配置文件中做以下声明来激活该后处理 Bean,如清单13所示:
清单13. 启用后处理Bean的配置
1 |
|
与前面类似,transaction-manager 属性的默认值是 transactionManager,若是事务管理器 Bean 的名字即为该值,则能够省略该属性。
虽然 @Transactional 注解能够做用于接口、接口方法、类以及类方法上,可是 Spring 小组建议不要在接口或者接口方法上使用该注解,由于这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。若是你在 protected、private 或者默承认见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
基于 <tx> 命名空间和基于 @Transactional 的事务声明方式各有优缺点。基于 <tx> 的方式,其优势是与切点表达式结合,功能强大。利用切点表达式,一个配置能够匹配多个方法,而基于 @Transactional 的方式必须在每个须要使用事务的方法或者类上用 @Transactional 标注,尽管可能大多数事务的规则是一致的,可是对 @Transactional 而言,也没法重用,必须逐个指定。另外一方面,基于 @Transactional 的方式使用起来很是简单明了,没有学习成本。开发人员能够根据须要,任选其中一种使用,甚至也能够根据须要混合使用这两种方式。
若是不是对遗留代码进行维护,则不建议再使用基于 TransactionInterceptor 以及基于TransactionProxyFactoryBean 的声明式事务管理方式,可是,学习这两种方式很是有利于对底层实现的理解。
虽然上面共列举了四种声明式事务管理方式,可是这样的划分只是为了便于理解,其实后台的实现方式是同样的,只是用户使用的方式不一样而已。
本教程的知识点大体总结以下: