1.背景
Spring提供了编程式事务和声明式事务,但由于编程性事务的侵入性,开发中普遍会使用Spring的声明式事务,下文中所说的Spring事务也都是指声明式事务。
Spring声明式事务底层是建立在AOP的基础上的,其本质就是对方法前后进行拦截,然后在目标方法之前创建或加入一个事务,在执行完目标方法之后根据执行执行情况提交或回滚事务。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,Spring事务提供两种事务管理方式:基于注解和基于XML配置,我们只需要简单的配置即可使用。
相比于编程式事务,由于声明式事务是基于AOP实现的,所以只能实现对方法的粗粒度的控制,而无法做到代码块级别的细粒度控制。
Spring事务包括5中参数,分别是:传播行为,隔离级别,是否只读,事务超时时间,回滚规则
本文分析下5种事务参数,然后对具体的传播行为给出Demo,以此给出其“细粒度”的事务实现。
2.Spring事务的5种参数
2.1 传播行为
传播行为定义了关于客户端和被调用方法的事务边界,Spring中定义了7中传播行为。
传播行为
|
意义
|
---|---|
PROPAGATION_REQUIRED | 默认的Spring事务传播规则;表示当前方法必须在一个事务中运行,如果一个现有事务正在运行中,该方法将在那个事务中运行,否则就会新建一个事务 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须在它自己的事务里运行。每次都会新建一个事务,如果当前有一个事务在运行的话,则将在这个方法运行期间挂起,待新事务执行完成后,上下文事务再恢复执行 |
PROPAGATION_SUPPORTS | 表示如果上下文中存在事务,就在当前事务中执行,否则使用非事务的方式执行 |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该在一个事务中运行。如果上下文中存在事务,它将在该方法执行期间被挂起 |
PROPAGATION_MANDATORY | 表示该方法必须在一个事务中运行,如果没有事务,将抛出异常 |
PROPAGATION_NEVER | 表示该方法不能在一个事务中运行,如果有事务,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前正有一个事务在运行中,则该方法会运行在一个嵌套事务中。被嵌套的事务可以独立于封装事务进行提交或回滚。如果不存在事务,则新建事务。 |
对嵌套事务的理解:
嵌套是子事务在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务也算是父事务的一部分,然后子事务执行结束,父事务继续执行。下面看几个问题就懂了
如果子事务回滚,会发生什么?
答:父事务会回滚到save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚
如果父事务回滚,会发生什么?
答:父事务回滚,子事务也会跟着回滚。父事务结束之前,子事务不会提交。
事务的提交是什么情况?
答:子事务是父事务的一部分,所以子事务先提交,父事务再提交
2.2 隔离级别
隔离级别定义一个事务可能接受其他并发事务活动受影响的程度。也可以想象成事务对于数据处理的自私程度。
多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题:
- 胀读——A事务读取数据并修改,未提交之间B事务又读取了数据
- 不可重复读——A事务读取数据,B事务读取数据并修改,A事务再读取数据时发现两次数据不一致
- 幻读——事务A读取或删除了全部数据,事务B又insert一条数据,这时事务A发现莫名其妙的多了一条数据
理想状态下,事务之间是完全隔离的。但是完全隔离会影响性能,因为隔离的实现依赖于数据库中的锁,侵占性锁会阻碍并发,要求事务互相等待
考虑到完全隔离会影响性能,而且并不是所有的情况都要求完全隔离,所以有时候可以在事务隔离方面灵活处理。因此,就有好几个隔离级别。
隔离级别
|
含义
|
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取未提交的更改。 可能导致脏读、幻读或不可重复读。 |
ISOLATION_READ_COMMITTED | 允许从已经提交的并发事务读取。可防止脏读,但幻读和不可重复读仍可能会发生。 |
ISOLATION_REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。 |
ISOLATION_SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。 |
2.3 是否只读
如果一个事务只对后端数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。
由于只读的优化措施是在一个事务启动时由后端数据库实施的,因此,只有对于那些具有可能启动一个新事物的传播行为(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义
2.4 事务超时
假设事务的运行时间变得格外的长,由于事务可能涉及对后端数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源,这时就可以声明一个事务在特定时间后自动回滚
由于超时时钟在一个事务启动的时候开始的,因此,