Spring 事务处理

事务介绍

事务(Transaction)是并发控制的单位,是用户定义的一系列操作组成的工作单元。该工作单元内的操作是不可分割的,要么都做,要么都不做。

事务特性

事务必需满足 ACID 特性

  • 原子性(Atomicity):事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做
  • 一致性(Consistency):在事务执行前数据处于正确的状态,而事务执行完成后数据还是处于正确的状态,即数据完整性约束没有被破坏
  • 隔离性(Isolation):并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性
  • 持久性(Durability):事务一旦执行成功,对数据的改变必须是永久的

事务隔离

多个事务并发执行(不完全事务隔离的情况)可能遇到问题如下:

  • 丢失更新(Lost update):两个事务同时更新一行数据,后一个事务的更新会覆盖掉前一个事务的更新,或者抛出异常更新失败
  • 脏读(Dirty read):一个事务读到了另一个事务未提交的更新数据,另一个事物会滚导致当前事物读到不存在的数据
  • 不可重复读(Unrepeatable read):在同一事务中,多次读取同一数据却返回不同的结果,也是由于另一个事务更新(或删除)该数据
  • 幻读(Phantom read):与不可重复度类似,区别在于,幻读是读取一批符合条件的数据,数据集合发生了变化

在 SQL 规范中定义了四种隔离级别:1 锁和隔离级别.md

  • 未提交读(Read Uncommitted):最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全
  • 读已提交(Read Committed):一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据
  • 可重复读(Repeatable Read):保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响
  • 序列化(Serializable):最高隔离级别,不允许事务并发执行,而必须串行化执行

事务传播

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

解释说明

PROPAGATION_REQUIRED

加入当前正要执行的事务,如果不在一个事务里,那么就起一个新的事务,例如:
ServiceB.methodB 的事务级别定义为 PROPAGATION_REQUIRED

情况一:ServiceA.methodA 已经起了事务,这时调用 ServiceB.methodB,ServiceB.methodB 就加入 ServiceA.methodA 的事务内部,就不再起新的事务。

情况二:ServiceA.methodA 没有在事务中,这时调用 ServiceB.methodB,ServiceB.methodB 就会为自己分配一个事务。

在 ServiceA.methodA 或者在 ServiceB.methodB 内的任何地方出现异常,事务都会被回滚。即使 ServiceB.methodB 的事务已经被提交,但是 ServiceA.methodA 在接下来要执行回滚,那么 ServiceB.methodB 也要回滚。

PROPAGATION_SUPPORTS

如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行。

PROPAGATION_MANDATORY

必须在一个事务中运行,只能被一个父事务调用。否则,就要抛出异常。

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。例如:
ServiceA.methodA 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB 的事务级别为 PROPAGATION_REQUIRES_NEW

当调用 ServiceB.methodB 的时候,ServiceA.methodA 所在的事务就会挂起,ServiceB.methodB 会起一个新的事务,等待 ServiceB.methodB 的事务完成以后,ServiceA.methodA 所在的事务才继续执行。

PROPAGATION_REQUIRES_NEW 与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度:

因为 ServiceB.methodB 和 ServiceA.methodA 是两个不同的事务。如果 ServiceB.methodB 已经提交,那么 ServiceA.methodA 失败回滚,ServiceB.methodB 是不会回滚的。如果 ServiceB.methodB 失败回滚,如果抛出的异常被 ServiceA.methodA 捕获,ServiceA.methodA 事务仍然可能提交。

PROPAGATION_NOT_SUPPORTED

以非事务方式执行,例如:
ServiceA.methodA 的事务级别是 PROPAGATION_REQUIRED ,ServiceB.methodB 的事务级别是 PROPAGATION_NOT_SUPPORTED

调用 ServiceB.methodB 时,ServiceA.methodA 的事务挂起,以非事务的状态运行完 ServiceB.methodB,再继续ServiceA.methodA 的事务。

PROPAGATION_NEVER

不能在事务中运行。
ServiceA.methodA 的事务级别是 PROPAGATION_REQUIRED,ServiceB.methodB 的事务级别是 PROPAGATION_NEVER,调用 ServiceB.methodB 抛出异常。

PROPAGATION_NESTED

理解 Nested 的关键是 Savepoint。与 PROPAGATION_REQUIRES_NEW 的区别是,PROPAGATION_REQUIRES_NEW 另起一个事务,将会与他的父事务相互独立,而 Nested 的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的,如果父事务最后回滚,他也要回滚的。Nested 事务的好处是有一个 Savepoint。

Spring 事务支持

Spring 框架核心功能之一就是事务管理,提供一致的事务管理抽象,对于不同的数据访问框架(如 Hibernate)通过实现接口 PlatformTransactionManager 支持各种数据访问框架的事务管理。

public interface PlatformTransactionManager {  
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;  
    void commit(TransactionStatus status) throws TransactionException;  
    void rollback(TransactionStatus status) throws TransactionException;  
}  

Spring 中关于事务配置由三个组成部分,分别是 DataSource、TransactionManager 和代理机制,Spring 事务管理涉及的接口如下:

Spring 并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给 Hibernate 或者 JTA 等持久化机制所提供的相关平台框架的事务来实现。
Spring 事务管理器的接口是 org.springframework.transaction.PlatformTransactionManager,通过这个接口,为各个平台如 JDBC、Hibernate 等都提供了对应的事务管理器,具体的实现就是各个平台自己的事情了。具体的事务管理机制对 Spring 来说是透明的,所以 Spring 事务管理的一个优点就是为不同的事务 API 提供一致的编程模型。

Spring 事务配置

Spring 提供了两种事务配置方式:编程式事务和声明式事务
编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于 AOP)有助于用户将操作与事务规则进行解耦。
也可以说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于 AOP,既能起到事务管理的作用,又可以不影响业务代码的具体实现。

声明式事务

<!-- JDBC 事务-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
    <property name="dataSource" ref="dataSource"/>  
</bean>

 <!-- JPA 事务-->
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">  
    <property name="entityManagerFactory" ref="entityManagerFactory"/>  
</bean> 

 <!-- Hibernate 事务--> 
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
    <property name="sessionFactory" ref="sessionFactory"/>  
</bean>  

编程式事务

Connection conn = null;
UserTransaction tx = null;
try {
    tx = getUserTransaction();                             // 1. 获取事务
    tx.begin();                                            // 2. 开启事务
    conn = getDataSource().getConnection();                // 3. 获取数据库连接
    String sql = "select * from INFORMATION_SCHEMA.SYSTEM_TABLES";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    ResultSet rs = pstmt.executeQuery();                   
    process(rs);                                          
    closeResultSet(rs);                             
    tx.commit();                                          // 4. 提交事务
} catch (Exception e) {
    tx.rollback();                                        // 5. 异常回滚事务
    throw e;
} finally {
   conn.close();                                          // 6. 关闭连接
}

声明式事务

根据代理机制的不同,总结了五种 Spring 事务的配置方式:

第一种:每个 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>

在 DAO 上需加上 @Transactional 注解,如下:

@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
    public List<User> listUsers() {
        return this.getSession().createQuery("from User").list();
    }
}

Spring 事务实现原理

Spring 事务管理机制实现的原理,是通过动态代理对所有需要事务管理的 Bean 进行加载,并根据配置在 invoke 方法中对当前调用的方法名进行判定,并在 method.invoke 方法前后为其加上合适的事务管理代码,这样就实现了 Spring 式的事务管理。Spring 中的 AOP 实现更为复杂和灵活,不过基本原理是一致的。

public class TxHandler implements InvocationHandler {
    private Object originalObject;

    public Object bind(Object obj) {
        this.originalObject = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        if (!method.getName().startsWith("save")) {
            UserTransaction tx = null;
            try {
                tx = (UserTransaction) (new InitialContext().lookup("java/tx"));
                result = method.invoke(originalObject, args);
                tx.commit();
            } catch (Exception ex) {
                if (null != tx) {
                    try {
                        tx.rollback();
                    } catch (Exception e) {
                    }
                }
            }
        } else {
            result = method.invoke(originalObject, args);
        }
        return result;
    }
}

Proxy.newProxyInstance 方法根据传入的接口类型动态构造一个代理类实例返回,这也说明了为什么动态代理实现要求其所代理的对象一定要实现一个接口。这个代理类实例在内存中是动态构造的,它实现了传入的接口列表中所包含的所有接口。

InvocationHandler.invoke 方法将在被代理类的方法被调用之前触发。通过这个方法,我们可以在被代理类方法调用的前后进行一些处理,如代码中所示 InvocationHandler.invoke 方法的参数中传递了当前被调用的方法(Method),以及被调用方法的参数。同时,可以通过 method.invoke 方法调用被代理类的原始方法实现。这样就可以在被代理类的方法调用前后写入任何想要进行的操作。


References:
https://blog.csdn.net/trigl/article/details/50968079
http://www.blogjava.net/robbie/archive/2009/04/05/264003.html

Tags:

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注

5 × 4 =