专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
芋道源码  ·  12月跳槽的兄弟注意了。。 ·  23 小时前  
JavaGuide  ·  Arrays.asList() ... ·  1 周前  
JavaGuide  ·  Java就业市场是真癫了。。 ·  1 周前  
芋道源码  ·  如果 MySQL 的自增ID用完了,怎么解决? ·  4 天前  
51好读  ›  专栏  ›  ImportNew

JavaEE – JPA(3):Spring Framework中的事务管理

ImportNew  · 公众号  · Java  · 2016-12-14 20:59

正文

(点击上方公众号,可快速关注)


来源:dm_vincent

链接:blog.csdn.net/dm_vincent/article/details/52615499


前文讨论了事务划分(Transaction Demarcation)在EJB中是如何实现的,本文继续介绍在spring Framework中是如何完成事务划分的。


我们已经知道了当采用Container事务类型的时候,事务划分主要有以下两种方案(参考这里):


  1. 使用JTA接口在应用中编码完成显式划分


  2. 在容器的帮助下完成自动划分


在使用JavaEE的EJB规范时,这两种方案分别被实现为BMT以及CMT,关于BMT和CMT在上一篇文章中有比较详尽的讨论(参考这里)。


那么对于Spring Framework而言,又是如何来实现上述两种方案的呢。


Spring Framework的主要优势之一就是屏蔽了底层容器的很多实现细节,它甚至会在JavaEE的标准之上进行一些封装,来最大程度地做到平台无关,容器无关。在使用Spring Framework开发JavaEE应用的时候,对底层容器的依赖并不明显。因此它并没有像EJB那样当采用基于Container的事务类型时,严重地依赖于JTA这一规范。它通过提供下面两种实现方案完成了对于事务的划分:


  1. 声明式的事务管理(Declarative Transaction Management)


  2. 编程式的事务管理(Programmatic Transaction Management)


虽然名字叫法不同,但是本质是差不多的。


第一种,声明式的事务管理,目的就是要帮助开发人员完成事务的自动划分。这一点和EJB CMT非常类似。第二种,编程式的事务管理,目的就是让开发人员能够有办法自行控制事务应该如何进行。这一点和EJB BMT非常类似。


Spring Framework事务管理概要


在介绍具体的声明式以及编程式这两种事务管理方案之前,还是需要简单介绍一下Spring Framework是如何进行事务管理的。


在传统的JavaEE中,开发人员一般可以采用两种事务类型:


  1. Resource-local 本地事务


  2. 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)。这也算是实现系统扩展性的一种经典设计模式了,具体可以参考维基百科。比如我们所熟知的JDBC就是依赖于这一模式,各种数据库厂商都实现了JDBC SPI中的功能从而使他们的数据库能够和Java应用通信。


接口中定义的每个方法都可能会抛出一个名为TransactionException的异常,这个异常不像是JTA的相关接口中定义的那些异常基本上都是受检异常(Checked Exception),而TransactionException是一个运行时异常(Runtime Exception)。这也反映出了Spring Framework的设计原则,尽量不给开发人员添乱。毕竟处理事务相关的异常可不是一门轻松活。一般而言都是直接抛出去,谁有能力处理交给谁吧。所以这里将异常定义为运行时异常默认了开发人员不会处理它,当然如果有能力处理的话加上try…catch语句就好了。


下面简要介绍一下上述接口中的三个方法:


TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;


这里有出现了两个新概念。


接受的参数是TransactionDefinition接口,返回的对象是TransactionStatus接口。


在TransactionDefinition接口的文档中,有这么一段话:


Interface that defines Spring-compliant transaction properties.

Based on the propagation behavior definitions analogous to EJB CMT attributes.


翻译一下就是:这个接口定义了Spring兼容的事务属性。这些属性类似于EJB CMT中关于事务传播(Transaction Propagation)行为的定义。而这个事务传播实际上就是指的@TransactionAttribute中定义的那些个属性,诸如MANDATORY,REQUIRED,REQUIRES_NEW等,详情可以参考这里。


知道了这一点,再来看看里面定义了些什么:


public interface TransactionDefinition {

    // 传播属性

    int PROPAGATION_REQUIRED = 0;

    int PROPAGATION_SUPPORTS = 1;

    int PROPAGATION_MANDATORY = 2;

    int PROPAGATION_REQUIRES_NEW = 3;

    int PROPAGATION_NOT_SUPPORTED = 4;

    int PROPAGATION_NEVER = 5;

    int PROPAGATION_NESTED = 6;

    // 隔离属性

    int ISOLATION_DEFAULT = -1;

    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;

    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    // 超时属性

    int TIMEOUT_DEFAULT = -1;

 

    // 行为

    int getPropagationBehavior();

    int getIsolationLevel();

    int getTimeout();

    boolean isReadOnly();

    String getName();

}


可见,这个接口里面定义了各种属性,同时也定义了获取一个事务属性的方法。所以将该接口作为上述getTransaction方法的参数,目的就很清晰了:根据所描述事务的属性来获取具体的事务。就好比传入一个config对象,得到一个符合该config定义的对象那样。


然后,方法返回的TransactionStatus又是啥呢:


public interface TransactionStatus extends SavepointManager, Flushable {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}


这就很明显了,它和之前介绍过的JTA提供用于实现BMT的UserTransaction相似度很高。它代表的就是一个当前执行线程所关联的一个具体事务对象。根据TransactionDefinition所定义的事务属性,这个具体的事务对象可以是一个刚刚创建的(当传播属性定义为PROPAGATION_REQUIRES_NEW时),也可以是复用的一个事务对象(当传播属性定义为PROPAGATION_SUPPORTS或者PROPAGATION_REQUIRED并且确实运行在事务环境中时)。


PlatformTransactionManager接口中剩下的两个方法commit()和rollback()看名字就知道它们怎么用了,因此不再一一介绍了。


PlatformTransactionManager接口的实现是需要根据具体的环境而被注入到运行时环境中的,比如JTA,JDBC,hibernate等。关于具体的注入方法可以参考Spring Framework的相关文档,这里就不再细说。


声明式的事务管理(Declarative Transaction Management)


值得一体的是,Spring Framework是通过AOP(面向切面编程,Aspect-Oriented Programming)技术来实现声明式的事务管理的。关于AOP,如果想要了解更多可以参考Wiki。


其实Spring Framework在实现声明式的事务管理时,多少也借鉴了一些EJB CMT的实现方式,取其精华去其糟粕。那么将它和EJB CMT比较的话,主要有以下几个方面的提高:


  1. 不再像EJB CMT那样局限于JTA。Spring Framework提供的声明式事务管理能够运行在几乎任何主流环境下,比如JTA,JDBC,Hibernate等。开发人员所需要做的只是根据底层环境提供相应的配置即可。


  2. 可以对任何类使用声明式事务管理,而不像EJB CMT那样只能对有限的几种类型,比如@Stateful,@Stateless等会话Bean。


  3. 声明式的回滚规则,比如指定抛出了哪些类型的异常时才发生回滚。这一特性是EJB CMT中不存在的。


  4. 通过AOP技术定制化事务行为,比如在事务发生回滚的时候执行自定义代码。而在EJB CMT中对于回滚你能做的仅仅是调用setRollbackOnly()。


和EJB CMT比较相近的一点是,它们都在发生了运行时异常(Runtime Exception)时会触发回滚操作,但是对于受检异常(Checked Exception)是不会主动回滚的,一般的理解是需要开发人员来处理,在catch语句中显式地执行回滚操作。


说的这么好听,那么如何使用声明式的事务管理呢。其实用法很简单,就两个步骤(以注解配置方式为例):


  1. 在@Configuration的JavaConfig类中使用@EnableTransactionManagement


  2. 在需要使用事务的类或者方法上使用@Transactional


要想理解它是如何实现的,首先需要了解AOP Proxy这一概念。也就是说,在开发人员声明了@Transactional之后,Spring Framework会利用AOP技术生成一个代理对象(Proxy),这个代理对象使用TransactionInterceptor并结合PlatformTransactionManager来实现事务的相关功能。


可以用下面这张图来表示:



在Spring Framework没有介入之前,只存在上图中的调用者和目标方法这两个部分。而在介入后利用AOP以及动态代理技术首先为方法所在对象创建了一个代理对象,然后根据配置生成各种AOP任务,如上图中的事务Advisor以及其它类型的Advisor(日志任务等)。然后在真正发生方法调用的时候,调用的顺序如上图中的数字1-8。在事务Advisor被执行的时候(步骤2)才会真正创建事务,然后在步骤4执行的是业务逻辑。随后执行流程开始依次返回,到步骤7发生的时候事务会根据其是否成功而提交或者是回滚。


对于具体的基于XML以及注解的配置方法,可以查看Spring Framework Transaction部分的相关文档。


编程式的事务管理(Programmatic Transaction Management)


Spring Framework提供了两种方法来支持编程式的事务管理:


  1. TransactionTemplate


  2. PlatformTransactionManager


推荐使用第一种方式。TransactionTemplate在形式上类似于Spring JDBC中提供的JdbcTemplate,封装了很多模板代码,让开发人员可以专注到业务逻辑的开发上。第二种类似于JTA中提供的UserTransaction,但是简化了部分异常处理。


利用TransactionTemplate完成编程式事务管理


下面是一段使用TransactionTemplate完成编程式事务管理的代码片段(下面的摘自Spring Framework官方文档):


public class SimpleService implements Service {

    // 共享的TransactionTemplate实例

    private final TransactionTemplate transactionTemplate;

    // 利用构造器注入将PlatformTransactionManager的实现注入到类中

    public SimpleService(PlatformTransactionManager transactionManager) {

        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");

        this.transactionTemplate = new TransactionTemplate(transactionManager);

    }

    public Object someServiceMethod() {

        return transactionTemplate.execute(new TransactionCallback() {

            // 此方法中的代码会在事务上下文中执行

            public Object doInTransaction(TransactionStatus status) {

                updateOperation1();

                return resultOfUpdateOperation2();

            }

        });

    }

}


使用了回调的风格完成了编程式的事务管理,其中比较关键的是TransactionCallback类的匿名实现,它作为参数传入到TransactionTemplate的execute方法中。


如果事务相关代码并没有返回值,那么可以使用不带参数的TransactionCallbackWithoutResult类的匿名实现。


transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    protected void doInTransactionWithoutResult(TransactionStatus status) {

        try {

            updateOperation1();

            updateOperation2();

        } catch (SomeBusinessExeption ex) {

            // 发生异常时,回滚事务

            status.setRollbackOnly();

        }

    }

});


利用PlatformTransactionManager完成编程式事务管理


可以在获取到PlatformTransactionManager的实现后通过下面的逻辑完成事务管理:


DefaultTransactionDefinition def = new DefaultTransactionDefinition();

// 只有在编程式事务管理中才能设置事务的名称

def.setName("SomeTxName");

def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);

try {

    // 执行业务逻辑

}

catch (MyException ex) {

    txManager.rollback(status);

    throw ex;

}

txManager.commit(status);


总结


本文简要介绍了Spring Framework中的事务管理。它是如何创建出和具体底层环境无关的抽象层,以及如何使用声明式/编程式来完成事务管理的。介绍的比较简要,资料参考自Spring Framework官方文档。如需要具体的配置方法和更多的例子,可以直接参考上述文档。


本系列:



觉得本文对你有帮助?请分享给更多人

关注「ImportNew」,提升Java技能