摘要

Spring框架是一个流行的基于轻量级控制反转容器的Java/J2EE应用框架,尤其在数据访问和事务管理方面的能力是众所周知的。Spring的声明性事务分离可以应用到任何POJO目标对象,并且包含所有EJB基于容器管理事务中的已声明事务。后台的事务管理器支持简单的基于JDBC的事务和全功能的基于JTA的J2EE事务。

这篇文章详细的讨论了Spring的事务管理特性。重点是如何在使用JTA作为后台事务策略的基础上让POJO利用Spring的声明性事务,这也显示了Spring的事务服务可以无缝地与J2EE服务器(如BEA WebLogic Server的事务协调器)的事务协调器进行交互,作为EJB CMT传统事务分离方式的一个替代者。

POJO的声明性事务

作为Spring声明性事务分离方式的样例,让我们来看一下Spring的样例应用PetClinic的中心服务外观中的配置:

清单1:

<bean id="dataSource"
   class="org.springframework.jndi.JndiObjectFactoryBean">
     <property name="jndiName">
        <value>java:comp/env/jdbc/petclinic</value>
     </property>
</bean>
<bean id="transactionManager"
   class="org.springframework.transaction.jta.JtaTransactionManager"/>
<bean id="clinicTarget"
   class="org.springframework.samples.petclinic.jdbc.JdbcClinic">
    <property name="dataSource"><ref bean="dataSource"/></property>
</bean>
<bean id="clinic"
   class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="target"><ref bean="clinicTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

他遵循Spring的标准XMLBean定义格式。定义了:

  1. 一个DataSource引用,指向一个JNDI位置—在J2EE服务器管理下这将从JNDI环境中获取特定的DataSource。
  2. 一个应用服务实现—这是一个POJO,封装了业务和数据访问逻辑。在这里实现了应用中的Clinic服务接口。
  3. 一个应用服务的事务代理—这个代理为目标服务定义了事务属性,匹配特定的方法名模式并为之创建相应的事务。在实际的事务管理中,代理指向一个PlatformTransactionManager实现。

注意:除了显式的代理定义,Spring还支持自动代理机制和通过Commons Attributes或J2SE 5.0注解实现源程序级的元数据使用。这些可选方法的讨论超过了本文的范围。可以参考Spring的文档来了解相关细节。

业务接口和业务实现是特定于应用的并且不需要关心Spring或者Spring的事务管理。普通Java对象可以作为服务的目标对象,而且任何普通Java接口可以作为服务的接口。下面是一个Clinic接口的示例:

清单2:

public interface Clinic {
    Pet loadPet(int id);
    void storePet(Pet pet);
    ...
}

这个接口的实现如下显示,假设他使用JDBC来执行必要的数据访问。他通过bean属性的设置方法来获取JDBC的DataSource;这与上面的配置中的dataSource属性定义相对应。

清单3:

public class JdbcClinic implements Clinic {
    private DataSource dataSource;
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    public Pet loadPet(int id) {
        try {
            Connection con = this.dataSource.getConnection();
            ...
        }
        catch (SQLException ex) {
            ...
        }
    }
    public void storePet(Pet pet) {
        try {
            Connection con = this.dataSource.getConnection();
            ...
        }
        catch (SQLException ex) {
            ...
        }
    }
    ...
}

如你所见,代码相当直接。我们使用一个简单的Java对象,而事务管理由事务代理来处理,这个我们会在下面讨论。
注意在PetClinic示例应用中实际的基于JDBC的Clinic实现利用了Spring的JDBC支持类来避免直接使用JDBC的API。虽然Spring的事务管理也可以与普通的基于JDBC实现一起工作,就向上面的示例。

定义事务代理

除了JdbcClinic实例以外,配置中也定义了一个事务代理。如果愿意这个代理所暴露的实际接口也可以显式定义。默认情况下,所有由目标对象实现的接口都暴露出来,在这个例子中就是应用的Clinic服务接口。

从客户端的观点来看,"clinic" bean只是这个应用的Clinic接口的实现。客户端不需要知道这会被一个事务代理所处理。这就是接口的能力:一个直接的目标对象的引用可以容易的被一个实现相同接口的代理所代替—在这儿就是一个隐式创建事务的代理。
代理的具体事务行为会由为根据特定的方法或方法命名模式而定义的事务属性来驱动,就像下面的例子所示:

清单3:

<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>

Key属性决定代理将为方法提供什么样的事务行为。这个属性的最重要部分就是事务传播行为。下面是一些可选的属性值:

  1. PROPAGATION_REQUIRED --支持当前的事务,如果不存在就创建一个新的。这是最常用的选择。
  2. PROPAGATION_SUPPORTS --支持当前的事务,如果不存在就不使用事务。
  3. PROPAGATION_MANDATORY --支持当前的事务,如果不存在就抛出异常。
  4. PROPAGATION_REQUIRES_NEW --创建一个新的事务,并暂停当前的事务(如果存在)。
  5. PROPAGATION_NOT_SUPPORTED --不使用事务,并暂停当前的事务(如果存在)。
  6. PROPAGATION_NEVER --不使用事务,如果当前存在事务就抛出异常。
  7. PROPAGATION_NESTED --如果当前存在事务就作为嵌入事务执行,否则与PROPAGATION_REQUIRED类似。

前6个事务策略与EJB的CMT类似,而且使用相同的常量名,因此对EJB开发人员来说是很亲切的。第7个策略PROPAGATION_NESTED是Spring提供的一个变体:他需要事务管理器(如DataSourceTransactionManager)提供类似JDBC3.0那样的保存点API来嵌套事务行为或者通过
JTA支持嵌套事务。

事务属性中的readOnly标识指示相应的事务应该作为一个只读事务来优化。这是一个优化提示:一些事务策略在这种情况下可以得到很好的性能优化,如使用ORM工具如Hibernate或TopLink时避免脏数据检查(“flush”尝试)。

在事务属性中还有一个“timeout”选项来定义事务的超时秒数。在JTA中,这个属性会简单地传递给J2EE服务器的事务协调器并被正确地解释。

使用事务代理

在运行时,客户端会取得一个“clinic”引用并转换为Clinic接口,然后调用如loadPet或storePet方法。这就隐式地使用了Spring的事务代理,通过“事务解释器”在目标对象中注册;这样一个新的事务就创建了,然后具体的工作就会代理给JdbcClinic的目标方法。
图1示例了一个使用“建议链”并到达最后目标的AOP代理的潜在概念。在这个示例中,唯一的建议是一个事务解释器用来包装目标方法的事务行为。这是一种用来在声明性事务功能下使用的基于代理的AOP。

Figure 1. An AOP proxy with an advisor chain and a target at the end

例如,一个PetClinic应用的WEB层组件可以执行ServletContext定位来获取Spring WebApplicationContext的引用并且获取受管理的“clinic”BEAN:

清单4:

WebApplicationContext ctx =
   WebApplicationContexUtils.getWebApplicationContext(servletContext);
Clinic clinic = (Clinic) ctx.getBean("clinic);
Pet pet = new Pet();
pet.setName("my new cat");
clinic.storePet(pet);

在调用storePet()之前,Spring的事务代理隐式地创建一个事务。当storePet()调用返回时,事务将提交或回滚。缺省情况下任何RuntimeException或Error将导致回滚。实际的提交或回滚可以是可以定义的:Spring的事务属性支持“回滚规则”的概念。

例如,我们可以可以引入一个强制的PetClinicException并且告诉事务代理在抛出异常时回滚:

清单5:

<prop key="load*">PROPAGATION_REQUIRED,readOnly,-PetClinicException</prop>
<prop key="store*">PROPAGATION_REQUIRED,-PetClinicException</prop>

这儿也有一个类似的“提交规则”语法,指示特定的异常将触发一次提交。
注意上面示例的显式定位引用的方法只是一种访问受Spring管理BEAN的方法的变化,可以用在任何WEB资源如servlet或filter。在构建基于Spring自身的MVC框架时,BEAN可以直接被注射到WEB控制器中。当然也支持在如Struts, WebWork, JSF, and Tapestry框架中访问Spring管理BEAN。详情可以参考Spring的文档。

PlatformTransactionManager策略

Spring事务支持的核心接口是org.springframework.transaction.PlatformTransactionManager。所有Spring的事务分离功能都会委托给PlatformTransactionManager(传给相应的TransactionDefinition实例)来做实际的事务执行。虽然PlatformTransactionManager接口可以直接调用,但通常应用只需要配置一个具体的事务管理器并且通过声明性事务来分离事务。

Spring提供几种不同的PlatformTransactionManager实现,分为如下两个类别:

  1. 本地事务策略—支持单一资源的事务(通常是单个数据库),其包括org.springframework.jdbc.datasource.DataSourceTransactionManager和 org.springframework.orm.hibernate.HibernateTransactionManager
  2. 全局事务管理—支持可能跨越多个资源的全局事务。其相应的类为org.springframework.transaction.jta.JtaTransactionManager,将事务委托给遵循JTA规范的事务协调器(通常为J2EE服务器,但不是强制的)。

PlatformTransactionManager抽象的主要价值在于应用不再被绑定在特定的事务管理环境。相反,事务策略可以很容易地切换—通过选择不同的PlatformTransactionManager实现类。这就使得应用代码与声明事务分离保持一致,而不需要考虑应用组件所使用的环境了。

例如,应用的初始版本可能布署在Tomcat上,与单个Oracle数据库交互。这可以方便地利用Spring的事务分离特性,只要选择基于JDBC的DataSourceTransactionManager作为使用的事务策略。Spring会分离事务,而JDBC驱动会执行相应的原始JDBC事务。

相同应用的另一个版本可能会布署在WebLogic服务器上,使用两个Oracle数据库。应用代码和事务分离不需要改变。唯一不同的是选择作为JtaTransactionManager事务策略,让Spring来分离事务而WebLogic服务器的事务协调器来执行事务。

JTA UserTransaction与JTA TransactionManager比较

让我们来看一下Spring对JTA支持的细节。虽然并非经常需要考虑这个细节但了解相关的细节还有必要的。对简单的用例如前面章节的示例,标准的JtaTransactionManager定义已经足够了,缺省的Spring JtaTransactionManager设置会从标准JNDI位置(J2EE规范所定义的java:comp/UserTransaction)获取JTA的javax.transaction.UserTransaction对象。这对大部分标准J2EE环境来说已经足够了。

然而,缺省的JtaTransactionManager不能执行事务暂停(也就是说不支持PROPAGATION_REQUIRES_NEW和PROPAGATION_NOT_SUPPORTED)。原因就在于标准的JTA UserTransaction接口不支持事务的暂停和恢复,而只支持开始和完成新的事务。

为了实现事务的暂停,需要一个javax.transaction.TransactionManager实例,他提供了JTA定义的标准的暂停和恢复方法。不幸的是,J2EE没有为JTA TransactionManager定义标准的JNDI位置!因此,我们需要使用厂商自己的定位机制。

清单6:

<bean id="transactionManager"
   class="org.springframework.transaction.jta.JtaTransactionManager">
     <property name="transactionManagerName">
        <value>vendorSpecificJndiLocation</value>
     </property>
</bean>

J2EE本质上没有考虑将JTA TransactionManager接口作为公共API的一部分。JTA规范自身定义了将TransactionManager接口作为容器集成的想法。虽然这是可以理解的,但是JTA TransactionManager的标准JNDI位置还是可以增加一定的价值,特别是对轻量级容器如Spring,这样任何J2EE服务器就可以用统一的方式来定位JTA TransactionManager了。

不仅Spring的JtaTransactionManager可以从访问中获益,O/R映射工具如Hibernate, Apache OJB, and Kodo JDO也能得到好处,因为他们需要在JTA环境中执行缓存同步的能力(释放缓存意味着JTA事务的完成)。这种注册事务同步的能力只有JTA TransactionManager接口才能提供,而UserTransaction是处理不了的。因此,这些工具都需要实现自己的TransactionManager定位器。

为JTA TransactionManager定义标准的JNDI位置是许多底层软件供应商最期望J2EE实现的功能。如果J2EE5.0的规范制定团队能够认识到这个特性的重要性就太好了。幸运地是,高级J2EE服务器如WebLogic Server已经考虑将JTA TransactionManager作为公共的API包含在扩展功能中。

在WebLogic JTA中实现Spring的事务分离

在WebLogic Server中,JTA TransactionManager官方的JNDI位置定义为javax.transaction.TransactionManager。这个值可以在Spring的JtaTransactionManager中作为“transactionManagerName”使用。原则上这样就可以在WebLogic's JTA系统中实现事务暂停了,也就是说支持PROPAGATION_REQUIRES_NEWPROPAGATION_NOT_SUPPORTED行为。

除了标准的JtaTransactionManager和其支持的通用配置选项外,Spring还提供了一个专用的WebLogicJtaTransactionManager适配器来直接利用WebLogic的JTA扩展。

在享受自动探测WebLogic的JTA TransactionManager的便利之外,他提供超越标准JTA的三个重要特性:

  1. 事务命名—暴露出Spring的事务名给WebLogic Server,使得Spring事务在WebLogic的事务监听器可见。缺省的,Spring会使用声明性事务的完整方法名。
  2. 每事务隔离级别—将Spring事务属性中定义的隔离级别应用到WebLogic JTA事务中。这使得每个事务都可以定义数据库的隔离级别,而这是标准JTA所不支持的。
  3. 强制事务恢复—即使在暂停的事务被标识为回滚时也可以恢复。这需要使用WebLogic的扩展TransactionManager接口来调用forceResume()方法。

Figure 2. WebLogic Server's transaction monitor (click the image for a full-size screen shot)

Spring的WebLogicJtaTransactionManager有效地为基于Spring的应用提供了WebLogic Server事务管理的全部功能。这使得Spring事务分离成为一种能与EJB CMT竟争的产品,而且提供了相同级别的事务支持。

Spring and EJB CMT

如上所示,Spring的POJO声明性事务分离可以作为一种除传统EJB CMT这外的选择。但是Spring与EJB并不是完成互斥的,Spring的应用上下文也可以作为EJB façade的后台来管理数据访问(DAO)和其他细纹理的业务对象。

在EJB情景中,事务是由EJB CMT来驱动的。对Spring来说,数据访问支持特性会自动检测到这样的环境并且采用相应的事务。例如,Spring对Hibernate的支持能够提供隐式的资源管理,即使是EJB驱动的事务,甚至可以在不需要修改任何DAO代码的情况下提供相同的语义。
Spring有效的解耦了DAO实现与实际的运行环境。DAO可以参与Spring的事务就像参与EJB CMT事务一样。这不仅简化在其他环境中的重用,而且更方便在J2EE容器外进行测试。

结论

Spring框架为J2EE和非J2EE环境提供了全量的事务分离的特性,特别表现在POJO的声明性事务上。他用一种灵活而非侵入式的方式为非EJB环境中的事务分离提供了便利。与EJB不同,这样的事务性POJO应用对象可以很容易的被测试和在J2EE容器外补重用。

Spring提供了各种事务策略,如JtaTransactionManager是用来代理J2EE服务器的事务协调器,而JDBC DataSourceTransactionManager是用来为简单的JDBC DataSource(就是单一目标数据库)执行事务。Spring可以很容易为不同的环境通过后台配置的简单修改来调整事务策略。

超越标准的JTA支持,Spring为WebLogic Server的JTA扩展提供了完善的集成,可以支持高级特性如事务监视和每事务隔离级别。通过对WebLogic Server的特殊支持,基于Spring的应用可以完全利用WebLogic Server的事务管理功能。

Spring事务分离是继EJB CMT之外的另一种可选方式,特别是对那些基于POJO的轻量级架构。在那只是因为选择LSSB(本地无状态会话BEAN)来应用声明性事务的情况下,基于Spring的POJO服务模型是一种可行的选择,他提供了非常高层的灵活性、可测试性和重用性。

详解阿里P7架构师是怎么在Spring中实现事务暂停的更多相关文章

  1. 转头条:阿里p7架构师:三年经验应该具备什么样的技能?

    问:工作中,有时候实现一个功能,会去看有没有现成的轮子可用.对于重复造轮子与改造轮子有什么看法? 答:一定会的,其实这也是一个提高技术能力的方法,比如今天想做个日期转换的功能,JDK8有日期的新特性就 ...

  2. 阿里P7架构师是如何解决跨域问题的!你有遇到吗?

    现在越来越多的项目就算是一个管理后端也偏向于使用前后端分离的部署方式去做,为了顺应时代的潮流,一前后端分离就产生了跨域问题,所以许多同学把跨域和前后端分离项目联系在了一起,其实跨域产生的原因并不是前后 ...

  3. 阿里P7架构师详解微服务链路追踪原理

    背景介绍 在微服务横行的时代,服务化思维逐渐成为了程序员的基本思维模式,但是,由于绝大部分项目只是一味地增加服务,并没有对其妥善管理,当接口出现问题时,很难从错综复杂的服务调用网络中找到问题根源,从而 ...

  4. 深入浅出!阿里P7架构师带你分析ArrayList集合源码,建议是先收藏再看!

    ArrayList简介 ArrayList 是 Java 集合框架中比较常用的数据结构了.ArrayList是可以动态增长和缩减的索引序列,内部封装了一个动态再分配的Object[]数组 这里我们可以 ...

  5. 阿里高级架构师教你使用Spring JMS处理消息事务源码案例

    消费者在接收JMS异步消息的过程中会发生执行错误,这可能会导致信息的丢失.该源码展示如何使用本地事务解决这个问题.这种解决方案可能会导致在某些情况下消息的重复(例如,当它会将信息储存到数据库,然后监听 ...

  6. 阿里高级架构师教你使用Spring Cloud Sleuth跟踪微服务

    随着微服务数量不断增长,需要跟踪一个请求从一个微服务到下一个微服务的传播过程,Spring Cloud Sleuth 正是解决这个问题,它在日志中引入唯一ID,以保证微服务调用之间的一致性,这样你就能 ...

  7. 阿里P8架构师深度概述分布式架构

    简介 作为一名架构师,我们要专业,要能看懂代码,及时光着臂膀去机房,也能独挡一面!及时同事搞不定问题,或者撂挑子,你也能给老大一个坚定的眼神:不怕,有我在!还能在会议室上滔滔不绝,如若无人,让不懂技术 ...

  8. 阿里P8架构师谈:MySQL慢查询优化、索引优化、以及表等优化总结

    更多内容:https://www.toutiao.com/i6599796228886626829/?tt_from=weixin&utm_campaign=client_share& ...

  9. 阿里P8架构师谈:数据库分库分表、读写分离的原理实现,使用场景

    本文转载自:阿里P8架构师谈:数据库分库分表.读写分离的原理实现,使用场景 为什么要分库分表和读写分离? 类似淘宝网这样的网站,海量数据的存储和访问成为了系统设计的瓶颈问题,日益增长的业务数据,无疑对 ...

随机推荐

  1. DRF 版本、认证、权限、限制、解析器和渲染器

    目录 一.DRF之版本控制 为什么要有版本控制? DRF提供的版本控制方案 版本的使用 全局配置 局部配置(使用较少) 二.DRF之认证 内置的认证 步骤 三.DRF之权限 1.自定义一个权限类 2. ...

  2. 修改SearchBar的取消按钮Cancel为中文

    一开始在网上看到很多方法都是循环,好吧,我也循环 创建UISearchBar的时候循环,不行 用searchBarTextDidBeginEditing事件去循环,也不行 无语了,搜索了Baidu第一 ...

  3. springboot2.x纯注解整合dubbo

    springboot1.x和springboot2.x整合差距挺大的,基于最新的2.x进行整合,使用纯注解的方式 依赖选取 首先pom文件的依赖引入,maven仓库有Apache和alibaba两个 ...

  4. 第八章 Fisco Bcos 国密版本的部署、控制台搭建、合约的部署、sdk 调用

    鉴于笔者以前各大博客教程都有很多人提问,早期建立一个技术交流群,里面技术体系可能比较杂,想了解相关区块链开发,技术提问,请加QQ群:538327407 参考资料 证书说明:https://mp.wei ...

  5. CDQZ集训DAY8 日记

    又一次翻车…… 先提一句昨晚的事.昨天晚上身后一帮成都七中的人用十分戏谑的语气交出了达哥的名字,看着NOI2017的获奖名单,如果他们真的是在嘲笑的话,真的挺想上去干他们一顿的…… 上午考试第一题一脸 ...

  6. ~~函数基础(七):生成器&迭代器~~

    进击のpython 生成器 上来说个这,就有点抽象了! 我们先整点活儿 宁,准备好了吗? 直接相位猛冲! 列表生成器 需求来了,老弟!我有一个数组 a = [1, 2, 3, 4, 5, 6, 7, ...

  7. C语言的指针移动怎么理解

    C Primer pkus(第五版)中文版,老外写的还是很经典的,推荐给朋友们,购买地址:C primer plus 5版中文版购买 另外再推荐本书: 程序员面试宝典(第5版)第五版:程序员面试宝典( ...

  8. MyBatis 一对多映射

    From<MyBatis从入门到精通> <!-- 6.1.2.1 collection集合的嵌套结果映射 和association类似,集合的嵌套结果映射就是指通过一次SQL查询将所 ...

  9. TCP、UDP和HTTP简述整理

    http:是用于www浏览的一个协议.tcp:是机器之间建立连接用的到的一个协议. 1.TCP/IP是个协议组,可分为三个层次:网络层.传输层和应用层.在网络层有IP协议.ICMP协议.ARP协议.R ...

  10. Linux基础学习整理

    linux学习记录 下载地址 centos 下载地址: 网易镜像:http://mirrors.163.com/centos/6/isos/ 搜狐镜像:http://mirrors.sohu.com/ ...