简介

Transaction在此指的是数据访问的事务,也就是Database transaction,可以理解为它是一组具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)的数据操作。对于J2EE来说,事务是一个不可或缺的组建模型,它保证了用户对数据操作的ACID属性。

事务的ACID特性

事务使用ACID特性来衡量事务的质量。介绍如下:

  1. 原子性 事务必须是原子的,在事务结束的时候,事务中的所有任务必须全部成功完成,否则全部失败,事务回滚到事务开始之间的状态。
  2. 一致性 事务必须保证和数据库的一致性,即数据库中的所有数据和现实保持一致。如果事务失败数据必须返回到事务执行之前的状态,反之修改数据和现实的同步。
  3. 隔离性 隔离性是事务与事务之间的屏障,每个事务必须与其他事务的执行结果隔离开,直到该事务执行完毕,它保证了事务的访问的任何数据不会受其他事务执行结果的影响。
  4. 持久性 如果事务成功执行,无论系统发生任何情况,事务的持久性都必须保证事务的执行结果是永久的。

利用JTA处理事务

提到事务我们都会联想到小明取钱的例子,再次就不赘述了,请自行脑补。

J2EE 事务处理方式

  • 本地事务: 紧密依赖于底层资源管理器(例如数据库连接),事务处理局限在当前事务资源内。此种事务处理方式不存在对应用服务器的依赖,因而部署灵活却无法支持多数据源的分布式事务。在数据库连接中使用本地事务示例如下:
public void transferAccount() { 
         Connection conn = null; 
         Statement stmt = null; 
         try{ 
             conn = getDataSource().getConnection(); 
             // 将自动提交设置为 false,
             //若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
             conn.setAutoCommit(false);

             stmt = conn.createStatement(); 
             // 将 A 账户中的金额减少 500 
             stmt.execute("\
             update t_account set amount = amount - 500 where account_id = 'A'");
             // 将 B 账户中的金额增加 500 
             stmt.execute("\
             update t_account set amount = amount + 500 where account_id = 'B'");

             // 提交事务
             conn.commit();
             // 事务提交:转账的两步操作同时成功
         } catch(SQLException sqle){            
             try{ 
                 // 发生异常,回滚在本事务中的操做
                conn.rollback();
                 // 事务回滚:转账的两步操作完全撤销
                 stmt.close(); 
                 conn.close(); 
             }catch(Exception ignore){ 

             } 
             sqle.printStackTrace(); 
         } 
     }
  • 分布式事务处理 : Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS;Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。分布式事务(Distributed Transaction)包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器承担着所有事务参与单元的协调与控制。JTA 事务有效的屏蔽了底层事务资源,使应用可以以透明的方式参入到事务处理中;但是与本地事务相比,XA 协议的系统开销大,在系统开发过程中应慎重考虑是否确实需要分布式事务。若确实需要分布式事务以协调多个事务资源,则应实现和配置所支持 XA 协议的事务资源,如 JMS、JDBC 数据库连接池等。使用 JTA 处理事务的示例如下(注意:connA 和 connB 是来自不同数据库的连接)
public void transferAccount() { 

         UserTransaction userTx = null; 
         Connection connA = null; 
         Statement stmtA = null; 

         Connection connB = null; 
         Statement stmtB = null; 

         try{ 
               // 获得 Transaction 管理对象
             userTx = (UserTransaction)getContext().lookup("\
                   java:comp/UserTransaction"); 
             // 从数据库 A 中取得数据库连接
             connA = getDataSourceA().getConnection(); 

             // 从数据库 B 中取得数据库连接
             connB = getDataSourceB().getConnection(); 

                        // 启动事务
             userTx.begin();

             // 将 A 账户中的金额减少 500 
             stmtA = connA.createStatement(); 
             stmtA.execute("
            update t_account set amount = amount - 500 where account_id = 'A'");

             // 将 B 账户中的金额增加 500 
             stmtB = connB.createStatement(); 
             stmtB.execute("\
             update t_account set amount = amount + 500 where account_id = 'B'");

             // 提交事务
             userTx.commit();
             // 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
         } catch(SQLException sqle){ 

             try{ 
                   // 发生异常,回滚在本事务中的操纵
                  userTx.rollback();
                 // 事务回滚:转账的两步操作完全撤销 
                 //( 数据库 A 和数据库 B 中的数据更新被同时撤销)

                 stmt.close(); 
                 conn.close(); 
                 ... 
             }catch(Exception ignore){ 

             } 
             sqle.printStackTrace(); 

         } catch(Exception ne){ 
             e.printStackTrace(); 
         } 
     }

JTA实现原理

JTA 体系架构

架构:它包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。 根据所面向对象的不同,我们可以将 JTA 的事务管理器和资源管理器理解为两个方面:面向开发人员的使用接口(事务管理器)和面向服务提供商的实现接口(资源管理器)。其中开发接口的主要部分即为上述示例中引用的 UserTransaction 对象,开发人员通过此接口在信息系统中实现分布式事务;而实现接口则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。以数据库为例,IBM 公司提供了实现分布式事务的数据库驱动程序,Oracle 也提供了实现分布式事务的数据库驱动程序, 在同时使用 DB2 和 Oracle 两种数据库连接时, JTA 即可以根据约定的接口协调者两种事务资源从而实现分布式事务。正是基于统一规范的不同实现使得 JTA 可以协调与控制不同数据库或者 JMS 厂商的事务资源。

开发人员使用开发人员接口,实现应用程序对全局事务的支持;各提供商(数据库,JMS 等)依据提供商接口的规范提供事务资源管理功能;事务管理器( TransactionManager )将应用对分布式事务的使用映射到实际的事务资源并在事务资源间进行协调与控制。

JTA 实现类图

  • UserTransaction开发人员通常只使用此接口实现 JTA 事务管理,

    • begin()- 开始一个分布式事务,(在后台 TransactionManager 会创建一个 Transaction 事务对象并把此对象通过 ThreadLocale 关联到当前线程上 )
    • commit()- 提交事务(在后台 TransactionManager 会从当前线程下取出事务对象并把此对象所代表的事务提交)
    • rollback()- 回滚事务(在后台 TransactionManager 会从当前线程下取出事务对象并把此对象所代表的事务回滚)
    • getStatus()- 返回关联到当前线程的分布式事务的状态 (Status 对象里边定义了所有的事务状态,感兴趣的读者可以参考 API 文档 )
    • setRollbackOnly()- 标识关联到当前线程的分布式事务将被回滚
  • Transaction代表了一个物理意义上的事务,在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程。UserTransaction 接口中的 commit()、rollback(),getStatus() 等方法都将最终委托给 Transaction 类的对应方法执行。

    • commit()- 协调不同的事务资源共同完成事务的提交
    • rollback()- 协调不同的事务资源共同完成事务的回滚
    • setRollbackOnly()- 标识关联到当前线程的分布式事务将被回滚
    • getStatus()- 返回关联到当前线程的分布式事务的状态
    • enListResource(XAResource xaRes, int flag)- 将事务资源加入到当前的事务中(在上述示例中,在对数据库 A 操作时 其所代表的事务资源将被关联到当前事务中,同样,在对数据库 B 操作时其所代表的事务资源也将被关联到当前事务中)
    • delistResourc(XAResource xaRes, int flag)- 将事务资源从当前事务中删除
    • registerSynchronization(Synchronization sync)- 回调接口,Hibernate 等 ORM 工具都有自己的事务控制机制来保证事务, 但同时它们还需要一种回调机制以便在事务完成时得到通知从而触发一些处理工作,如清除缓存等。这就涉及到了 Transaction 的回调接口 registerSynchronization。工具可以通过此接口将回调程序注入到事务中,当事务成功提交后,回调程序将被激活。
  • TransactionManager本身并不承担实际的事务处理功能,它更多的是充当用户接口和实现接口之间的桥梁。
    在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程上;同样 UserTransaction.commit() 会调用 TransactionManager.commit(), 方法将从当前线程下取出事务对象 Transaction 并把此对象所代表的事务提交, 即调用 Transaction.commit()

    • begin()- 开始事务
    • commit()- 提交事务
    • rollback()- 回滚事务
    • getStatus()- 返回当前事务状态
    • setRollbackOnly()
    • getTransaction()- 返回关联到当前线程的事务
    • setTransactionTimeout(int seconds)- 设置事务超时时间
    • resume(Transaction tobj)- 继续当前线程关联的事务
    • suspend()- 挂起当前线程关联的事务

代码示例

开始事务

UserTransactionImpl implenments UserTransaction

public void begin() throws NotSupportedException, SystemException { 
   // 将开始事务的操作委托给 TransactionManagerImpl 
   TransactionManagerImpl.singleton().begin(); 
     }

TransactionManagerImpl implements TransactionManager

// 此处 transactionHolder 用于将 Transaction 所代表的事务对象关联到线程上
private static ThreadLocal<TransactionImpl> transactionHolder 
        = new ThreadLocal<TransactionImpl>(); 

     //TransacationMananger 必须维护一个全局对象,因此使用单实例模式实现
     private static TransactionManagerImpl singleton = new TransactionManagerImpl(); 

     private TransactionManagerImpl(){ 

     } 

     public static TransactionManagerImpl singleton(){ 
         return singleton; 
     } 

     public void begin() throws NotSupportedException, SystemException { 
         //XidImpl 实现了 Xid 接口,其作用是唯一标识一个事务
         XidImpl xid = new XidImpl(); 
         // 创建事务对象,并将对象关联到线程
         TransactionImpl tx = new TransactionImpl(xid); 

         transactionHolder.set(tx); 
     }

由于Transaction 对象本身就代表了一个事务,在它被创建的时候就表明事务已经开始,因此也就不需要额外定义 begin() 方法了。

提交事务

UserTransactionImpl implenments UserTransaction

public void commit() throws RollbackException, HeuristicMixedException, 
             HeuristicRollbackException, SecurityException, 
             IllegalStateException, SystemException { 

             // 检查是否是 Roll back only 事务,如果是回滚事务
                if(rollBackOnly){ 
                 rollback(); 

                 return; 
               } else { 
                // 将提交事务的操作委托给 TransactionManagerImpl 
                TransactionManagerImpl.singleton().commit(); 
               } 
     }

TransactionManagerImpl implenments TransactionManager

public void commit() throws RollbackException, HeuristicMixedException, 
    HeuristicRollbackException, SecurityException, 
    IllegalStateException, SystemException { 

     // 取得当前事务所关联的事务并通过其 commit 方法提交
     TransactionImpl tx = transactionHolder.get(); 
     tx.commit(); 
             }

同理, rollback、getStatus、setRollbackOnly 等方法也采用了与 commit() 相同的方式实现。 UserTransaction 对象不会对事务进行任何控制, 所有的事务方法都是通过 TransactionManager 传递到实际的事务资源即 Transaction 对象上。

JTA事物处理的例子

上述示例演示了 JTA 事务的处理过程,下面将为您展示事务资源(数据库连接,JMS)是如何以透明的方式加入到 JTA 事务中的。首先需要明确的一点是,在 JTA 事务 代码中获得的数据库源 ( DataSource ) 必须是支持分布式事务的。在如下的代码示例中,尽管所有的数据库操作都被包含在了 JTA 事务中,但是因为 MySql 的数据库连接是通过本地方式获得的,对 MySql 的任何更新将不会被自动包含在全局事务中。

public void transferAccount() { 

         UserTransaction userTx = null; 
         Connection mySqlConnection = null; 
         Statement mySqlStat = null; 

         Connection connB = null; 
         Statement stmtB = null; 

         try{ 
                // 获得 Transaction 管理对象
             userTx = 
            (UserTransaction)getContext().lookup("java:comp/UserTransaction");
             // 以本地方式获得 mySql 数据库连接
             mySqlConnection = DriverManager.getConnection("localhost:1111"); 

             // 从数据库 B 中取得数据库连接, getDataSourceB 返回应用服务器的数据源
             connB = getDataSourceB().getConnection(); 

                        // 启动事务
             userTx.begin();

             // 将 A 账户中的金额减少 500 
             //mySqlConnection 是从本地获得的数据库连接,不会被包含在全局事务中
             mySqlStat = mySqlConnection.createStatement(); 
             mySqlStat.execute("
             update t_account set amount = amount - 500 where account_id = 'A'");

             //connB 是从应用服务器得的数据库连接,会被包含在全局事务中
             stmtB = connB.createStatement(); 
             stmtB.execute("
             update t_account set amount = amount + 500 where account_id = 'B'");

             // 事务提交:connB 的操作被提交,mySqlConnection 的操作不会被提交
             userTx.commit();

         } catch(SQLException sqle){ 
             // 处理异常代码
         } catch(Exception ne){ 
             e.printStackTrace(); 
         } 
     }

关于JTA事务本文不再赘述,详细资料请看JTA 深度历险 - 原理与实现

Spring事务处理机制

支持两种事务声明方式分别是编程式事务与声明式事务,当然无论你选择上述何种事务方式去实现事务控制,spring都提供基于门面设计模式的事务管理器供选择,如下表

事务管理器实现(org.springframework.*) 使用时机
jdbc.datasource.DataSourceTransactionManager 使用jdbc的抽象以及ibatis支持
orm.hibernate.HibernateTransactionManager 使用hibernate支持(默认3.0以下版本)
orm.hibernate3.HibernateTransactionManager 使用hibernate3支持
transaction.jta.JtaTransactionManager 使用分布式事务(分布式数据库支持)
orm.jpa.JpaTransactionManager 使用jpa做为持久化工具
orm.toplink.TopLinkTransactionManager 使用TopLink持久化工具
orm.jdo.JdoTransactionManager 使用Jdo持久化工具
jms.connection.JmsTransactionManager 使用JMS 1.1+
jms.connection.JmsTransactionManager102 使用JMS 1.0.2
transaction.jta.OC4JJtaTransactionManager 使用oracle的OC4J JEE容器
transaction.jta.WebLogicJtaTransactionManager 在weblogic中使用分布式数据库
jca.cci.connection.CciLocalTransactionManager 使用jrping对J2EE Connector Architecture (JCA)和Common Client Interface (CCI)的支持

UML结构图如下

各种事务配置方法

  • JdbcTransactionManager定义如下
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
</bean>
  • hibernate事务管理器配置如下
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

hibernate的事务管理器会注入session会话工厂,然后将事务处理委托给当前的transaction对象,事务提交时,调用commit()方法,回滚时调用rollback()方法

  • jpa事务管理器配置如下
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
  • ...

声明式事务配置

spring特有的事务传播行为,spring支持7种事务传播行为,确定客户端和被调用端的事务边界(说得通俗一点就是多个具有事务控制的service的相互调用时所形成的复杂的事务边界控制)下图所示为7钟事务传播机制

传播行为 含义
PROPAGATION_REQUIRED(XML文件中为REQUIRED) 表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚)
PROPAGATION_SUPPORTS(XML文件中为SUPPORTS)
PROPAGATION_MANDATORY(XML文件中为MANDATORY)
PROPAGATION_NESTED(XML文件中为NESTED)
PROPAGATION_NEVER(XML文件中为NEVER)
PROPAGATION_REQUIRES_NEW(XML文件中为REQUIRES_NEW)
PROPAGATION_NOT_SUPPORTED(XML文件中为NOT_SUPPORTED)

spring中的事务隔离级别

spring的事务隔离级别其实本质上是对SQL92标准的4种事务隔离级别的一种封装.
|隔离级别| 含义 |
| :-----: | :---: |
| ISOLATION_DEFAULT | 使用数据库默认的事务隔离级别 |
| ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的修改,可能导致脏读、幻读和不可重复读 |
| ISOLATION_READ_COMMITTED | 允许从已经提交的事务读取,可防止脏读、但幻读,不可重复读仍然有可能发生 |
| ISOLATION_REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务自生修改。可防止脏读和不可重复读,但幻读仍有可能发生 |
| ISOLATION_SERIALIZABLE | 完全服从ACID隔离原则,确保不发生脏读、不可重复读、和幻读,但执行效率最低。 |

Spring中的事务配置

Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。
DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager。
具体如下图:

此处输入图片的描述

  • 第一种方式:每个Bean都有一个代理

    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
    
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <bean id="userDao"  
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
           <!-- 配置事务管理器 -->  
           <property name="transactionManager" ref="transactionManager" />     
        <property name="target" ref="userDaoTarget" />  
         <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- 配置事务属性 -->  
        <property name="transactionAttributes">  
            <props>  
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>  
        </property>  
    </bean>
  • 第二种方式:所有Bean共享一个代理基类

    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
    
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <bean id="transactionBase"  
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"  
            lazy-init="true" abstract="true">  
        <!-- 配置事务管理器 -->  
        <property name="transactionManager" ref="transactionManager" />  
        <!-- 配置事务属性 -->  
        <property name="transactionAttributes">  
            <props>  
                <prop key="*">PROPAGATION_REQUIRED</prop>  
            </props>  
        </property>  
    </bean>    
    
    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <bean id="userDao" parent="transactionBase" >  
        <property name="target" ref="userDaoTarget" />   
    </bean>
  • 第三种方式:使用拦截器

    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
    
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 
    
    <bean id="transactionInterceptor"  
        class="org.springframework.transaction.interceptor.TransactionInterceptor">  
        <property name="transactionManager" ref="transactionManager" />  
        <!-- 配置事务属性 -->  
        <property name="transactionAttributes">  
            <props>  
                <prop key="*">PROPAGATION_REQUIRED</prop>  
            </props>  
        </property>  
    </bean>
    
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  
        <property name="beanNames">  
            <list>  
                <value>*Dao</value>
            </list>  
        </property>  
        <property name="interceptorNames">  
            <list>  
                <value>transactionInterceptor</value>  
            </list>  
        </property>  
    </bean>  
    
    <!-- 配置DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
  • 第四种方式:使用tx标签配置的拦截器

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />
    
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
    
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />        
    </aop:config>
  • 第五种方式:全注解

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />
    
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
    
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>