一、概述

       在Spring中数据库事务是通过PlatformTransactionManager(事务平台管理)进行管理的。TransactionTemplate是Spring所提供的事务管理器的模板,先看看一段TransactionTemplate的源码

//事务管理器
private PlatformTransactionManager transactionManager;
//.....
@Override
public Object execute(TransactionCallback action) throws TransactionException {
    //使用自定义的事务管理器
	if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager)         
    {
		return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
	}
	else {//系统默认管理器
                //获取事务状态
		TransactionStatus status = this.transactionManager.getTransaction(this);
		Object result = null;
		try {
                       //回调接口方法
			result = action.doInTransaction(status);
		}catch (RuntimeException ex) {
				 // 回滚异常方法
				rollbackOnException(status, ex);
                                 //抛出异常
				throw ex;
		}catch (Error err) {
				// 回滚异常方法
				rollbackOnException(status, err);
                                //抛出异常
				throw err;
		}catch(Throwable ex){
                                // 回滚异常方法
				rollbackOnException(status, err);
                                //抛出无法捕获异常
				throw new UndeclaredThrowableException(ex,
                            "抛出无法捕获异常");
        }
                //没有异常,提交事务
		this.transactionManager.commit(status);
                //返回结果
		return result;
		}
	}

源码我们可以知道:

1)事务的创建、提交和回滚由PlatformTransactionManager接口来完成;

2)只要产生异常,就回滚。

3)  没有异常,提交事务,并返回结果。

其实PlatformTransactionManager接口源码很简单,就三个方法

public interface PlatformTransactionManager {

	//根据事务定义TransactionDefinition,获取事务
	TransactionStatus getTransaction(TransactionDefinition definition);

	//提交事务
	void commit(TransactionStatus status);

	//回滚事务
	void rollback(TransactionStatus status);
}

Spring的事务体系也是在PlatformTransactionManager事务管理器接口上开展开来的,事务的提交、回滚等操作全部交给它来实现。

二、配置事务管理器

    MyBatis框架中用的最多的就是DataSourceTransactionManager。它与PlatformTransactionManager的关系如下:

  • extends
  • implemets

(1)使用XML进行配置事务管理器:不常用,通常用注解的方式。

<!-- 配置事务管理器 -->
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <!-- 装配数据源 -->
     <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!--使用xml配置事务方法 -->
<aop:config>
     <!-- 设置添加事务的方法,使用切入点表达式-->
     <aop:pointcut expression="execution(* *.checkout(..))" id="mypoint"/>
     <!-- 将事务方法和事务的相关配置关联起来 -->
     <aop:advisor advice-ref="myAdvice" pointcut-ref="mypoint" />
</aop:config>
<!-- tx配置事务的属性 (使用tx名称空间)-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
     <tx:attributes>
       <!-- 配置事务的属性,多个事务方法也可以在这个里面放,name设置事务方法名,propagation设置事务相关信息 -->
        <tx:method name="checkout" propagation="REQUIRED"/>
     </tx:attributes>
</tx:advice>

(2)基于注解的事务配置

  • 首先在需要添加事务的方法上加上@Transactional注解
  • 还需要Spring的配置文件中配置事务管理器
<!-- 添加事务管理器组件DataSourceTransactionManager -->
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 使用set方法注入数据源 -->
     property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 开启基于注解声明式事务 注意配置transaction-manager属性,它引用了我们事务管理组件对象,这里要和事务管理器组件id一致 默认是transactionManager -->
<tx:annotation-driven transaction-manager="transactionManager" />

 三、声明式事务的约定流程

          当Spring IoC容器初始化时,Spring 会读入@Transactional 或者 XML配置的事务信息,并保存到一个事务定义类(TransactionDefinition接口的子类),以备使用。当运行时会让Spring拦截注解标注的某一个方法或者类的使用所有方法。

          Spring提供事务管理器创建事务,把事务中定义的隔离级别、超时时间等属性根据配置内容往事务上配置。根据传播行为采取特定的措施。

         在整个开发过程中,我们只需要编写业务代码和对事务属性进行配置就可以。

四、选择传播行为和隔离特性

        上面说到,我们只需要编写业务代码和对事务属性进行配置就可以,那毫无疑问,怎么配置才是重点!首先先了解下数据库事务的特性。

1、数据库事务正确执行的4个基础要素:

  • 1)原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 2)一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。例如,完整性约束了a+b=10,一个事务改变了a,那么b也应该随之改变。
  • 3)隔离性:两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。
  • 4)持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并且不会被回滚。

2、其他三个都比较好理解,重点说隔离性,隔离性为了解决的问题主要有三个:通过设置隔离级别的方式

(1)、 脏读(Drity Read):事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,如果事务A提交失败,事务B读到的就是脏数据。同时也有脏读也是最低级别的隔离级别。

                              

spring 无事务 数据库连接不释放_回滚

为了克服脏读,SQL标注提出了第二个隔离级别:读/写提交:就是说一个事务只能读取另一个事务已经提交的事务。

                             

spring 无事务 数据库连接不释放_事务管理_02

          但是也会引发其他问题:

                          

spring 无事务 数据库连接不释放_回滚_03

额是一个变化的值。我们称之为不可重复读。 

(2)、不可重复读(Non-repeatable read) : 在同一个事务中,对于同一份数据读取到的结果不一致。比如,事务B在事务A提交前读到的结果,和提交后读到的结果可能不同。不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这导致锁竞争加剧,影响性能。

为了克服不可重读带来的错误,SQL标准又提出了一个隔离级别:可重复读:针对数据库的同一条记录的读/写按照一个序列化操作,不会产生交叉情况,保证数据一致性。可以认为是锁住这个记录。

  但是也会产生一些问题 : 产生幻读

                        

spring 无事务 数据库连接不释放_隔离级别_04

(3)、幻读(Phantom Read) : 在同一个事务中,同一个查询多次返回的结果不一致。事务A新增了一条记录,事务B在事务A提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录。幻读仅指由于并发事务增加记录导致的问题,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。

(4)、最后,为了克服幻读,SQL推出更高的隔离级别----序列化。

总结这三种情况对应的四种隔离级别:

                    

spring 无事务 数据库连接不释放_回滚_05

3、怎么选择隔离级别

 (1)随着隔离级别的提高,系统性能逐渐下降(“高风险,高回报”)。

 (2)序列化会严重抑制并发,从而引发大量的线程挂起,知道获取锁才能进行下一步操作,恢复时又需要等待大量的时间,但是数据绝对安全。可以在并发量不大但又保证数据安全性的情境下使用。

 (3)企业一般选择读/写提交模式。

 (4)MySql支持4中隔离级别,默认为可重复读,Oracle支持读/写提交和可重复读,默认为读/写提交。

 总之:隔离级别需要根据并发量的大小和性能来决定。

                       

spring 无事务 数据库连接不释放_事务管理_06

4、传播行为

     事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 
    例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

    Spring的7种传播行为:

   

spring 无事务 数据库连接不释放_spring 无事务 数据库连接不释放_07

其中REQUIRED:这是最常见的,也是Spring 默认的传播行为。它比较简单,支持当前事务,如果没有事务,就建一个新的事务。

一般而言,企业比较关注的是下面这两种:

1、REQUIRES_NEW:当一个REQUIRED方法A调用REQUIRES_NEW方法B时,B方法会新new一个事务,并且这个事务和A事务没有关系,也就是说B方法出现异常,不会导致A的回滚,同理当B已提交,A再出现异常,B也不会回滚。

2、NESTED:这个和REQUIRES_NEW的区别是B方法的事务和A方法的事务是相关的。只有在A事务提交的时候,B事务都会提交。也就是说当A发生异常时,A、B事务都回滚,而当B出现异常时,B回滚,而A回滚到savepoint。