前文讨论了事务划分(Transaction Demarcation)在EJB中是如何实现的,本文继续介绍在Spring Framework中是如何完成事务划分的。
我们已经知道了当采用Container事务类型的时候,事务划分主要有以下两种方案(参考这里):
- 使用JTA接口在应用中编码完成显式划分
- 在容器的帮助下完成自动划分
在使用JavaEE的EJB规范时,这两种方案分别被实现为BMT以及CMT,关于BMT和CMT在上一篇文章中有比较详尽的讨论(参考这里)。
那么对于Spring Framework而言,又是如何来实现上述两种方案的呢。
Spring Framework的主要优势之一就是屏蔽了底层容器的很多实现细节,它甚至会在JavaEE的标准之上进行一些封装,来最大程度地做到平台无关,容器无关。在使用Spring Framework开发JavaEE应用的时候,对底层容器的依赖并不明显。因此它并没有像EJB那样当采用基于Container的事务类型时,严重地依赖于JTA这一规范。它通过提供下面两种实现方案完成了对于事务的划分:
- 声明式的事务管理(Declarative Transaction Management)
- 编程式的事务管理(Programmatic Transaction Management)
虽然名字叫法不同,但是本质是差不多的。
第一种,声明式的事务管理,目的就是要帮助开发人员完成事务的自动划分。这一点和EJB CMT非常类似。第二种,编程式的事务管理,目的就是让开发人员能够有办法自行控制事务应该如何进行。这一点和EJB BMT非常类似。
Spring Framework事务管理概要
在介绍具体的声明式以及编程式这两种事务管理方案之前,还是需要简单介绍一下Spring Framework是如何进行事务管理的。
在传统的JavaEE中,开发人员一般可以采用两种事务类型:
- Resource-local 本地事务
- Global 全局事务 (一般需要应用服务器提供的容器环境,所以也称为Container事务)
对于本地事务而言,它没有办法针对多个事务性资源,往往只能针对单一的事务资源。而且它也过于底层,对于业务逻辑的侵入性很强,这一点写过基于JDBC应用的同学都知道,20行代码里面可能只有5行是业务相关的,剩下15行都用来处理事务和相关异常了。所以这样的代码很难维护,看上去也不那么优雅。
对于全局事务而言,它克服了本地事务的缺点,能够处理多个事务性资源(典型的比如数据库,消息队列等)。但是它依赖于笨重的JTA(Java Transaction API),这一套和事务相关的API用起来也是让人叫苦不迭,和JDBC类似,都有太过底层的问题。更重要的是,JTA一般是应用服务器才会提供的服务,因此使用全局事务的条件也算是比较苛刻。
所以针对以上的种种痛点,Spring Framework建立了一套关于事务的抽象。让开发人员可以在任何环境中方便地使用事务来管理资源。甚至在一般情况下连应用服务器也不需要了,使用一般的Web容器即可,比如流行的Tomcat(只有在需要同时处理多个事务性资源的情况下,才可能需要使用应用服务器,但是一般的应用显然没有那么复杂)。
Spring Framework对事务的抽象
Spring Framework通过引入一个名为事务策略(Transaction Strategy)的概念来建立这个抽象。具体而言,表现为下面这个接口:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
org.springframework.transaction.PlatformTransactionManager
接口本质上是一个服务提供接口(Service Provider Interface, SPI)。这也算是实现系统