spring 事务的传播级别和隔离级别

Posted 晓风残月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring 事务的传播级别和隔离级别相关的知识,希望对你有一定的参考价值。

1.事务的传播级别

1)@Transactional(propagation=Propagation.REQUIRED):默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行,所以这个级别通常能满足处理大多数的业务场景。

2)@Transactional(propagation=PROPAGATION.SUPPORTS):从字面意思就知道,supports(支持),该传播级别的特点是,如果上下文存在事务,则支持当前事务,加入到事务执行,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作,应用场景较少。

3)@Transactional(propagation=PROPAGATION.MANDATORY):该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。

4)@Transactional(propagation=PROPAGATION.REQUIRES_NEW):从字面即可知道,每次都要一个新的事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,当新建事务执行完成以后,上下文事务再恢复执行。
这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个PROPAGATION.REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。

5)@Transactional(propagation=PROPAGATION.NOT_SUPPORTED) :这个也可以从字面得知,not supported(不支持),当前级别的特点是,如果上下文中存在事务,
则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了,用当前级别的事务模板抱起来就可以了。

6)@Transactional(propagation=PROPAGATION.NEVER):该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!

7)@Transactional(propagation=PROPAGATION.NESTED):字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
那么什么是嵌套事务呢?

嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point,看几个问题就明白了。
如果子事务回滚,会发生什么? 
如果子事务抛出的异常被父事务捕获,则父事务仍然可以提交;如果没有捕获,则父事务也将回滚。

如果父事务回滚,会发生什么? 

父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。
那么:事务的提交,是什么情况? 是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?
答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交。

以上是事务的7个传播级别,在日常应用中,通常可以满足各种业务需求,但是除了传播级别,在读取数据库的过程中,如果两个事务并发执行,那么彼此之间的数据是如何影响的呢?

这就需要了解一下事务的另一个特性:事务的隔离级别。

2.事务的隔离级别

1)@Transactional(isolation = Isolation.SERIALIZABLE):最严格的级别,事务串行执行,资源消耗最大; 
2)@Transactional(isolation = Isolation.REPEATABLE_READ):保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。 
3)@Transactional(isolation = Isolation.READ_COMMITTED):大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,该级别适用于大多数系统。 
4)@Transactional(isolation = Isolation.READ_UNCOMMITTED):保证了读取过程中不会读取到非法数据。

1:Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。 
2:non-repeatable reads--不可重复读。比如事务A中两处读取同一行数据的total值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A在同一个事务中重复读取的结果不一致,也就是所谓的不可重复读。 
3:phantom reads--幻象读数据。这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据被修改了,但是phantom reads所要读的数据的不一致指的是:相同的查询语句执行两次,但第二次返回第一次没有返回的行,则该行是“幻像”行,也就是出现了幻读。比如SELECT * FROM child WHERE id > 100 FOR UPDATE;,此时表中有id=90,id=102两行数据。事务A第一次读取时返回了1行数据,之后事务B往该表插入了一条id=101的数据,事务第二次读取的时候,结果返回了2行数据,则出现了幻读。

 

不可重复读的重点是修改:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了。

幻读的重点在于新增或者删除:同样的条件, 第1次和第2次读出来的记录数不一样。

而事务的隔离级别会导致读取到非法数据的情况如下表示:

常用数据库默认事务隔离级别

mysql:默认为REPEATABLE_READ

SQLSERVER:默认为READ_COMMITTED

ORACLE:默认为READ_COMMITTED

3.@Transactional注解中常用参数说明

1.)readOnly

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)

2.)rollbackFor

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:

指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

3.)rollbackForClassName

该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:

指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException")

指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})

4.)noRollbackFor

该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:

指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})

5.)noRollbackForClassName

该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:

指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException")

指定多个异常类名称:

@Transactional(noRollbackForClassName={"RuntimeException","Exception"})

propagation

该属性用于设置事务的传播行为,具体取值可参考表6-7。

例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

isolation

该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置

6.)timeout

该属性用于设置事务的超时秒数,默认值为-1表示永不超时

4.注意的几点:

1)@Transactional只能被应用到public方法上,对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.

2)用spring事务管理器,由spring来负责数据库的打开,提交,回滚。默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常})。如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)
如下:
@Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚
public void methodName() {
throw new Exception("注释");

}
@Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚
public ItimDaoImpl getItemDaoImpl() {
throw new RuntimeException("注释");
}

3)@Transactional注解应该只被应用到public可见度的方法上。如果你在 protected、private或者package-visible的方法上使用@Transactional注解它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

4)@Transactional注解可以被应用于接口定义和接口方法、类定义和类的public方法上。然而,请注意仅仅@Transactional注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别@Transactional注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 <tx:annotation-driven/>元素的出现 开启 了事务行为。

5)Spring团队的建议是你在具体的类(或类的方法)上使用@Transactional注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。

以上是关于spring 事务的传播级别和隔离级别的主要内容,如果未能解决你的问题,请参考以下文章

Spring事务传播性与隔离级别

浅析Spring事务传播行为和隔离级别

Spring支持的常用数据库事务传播属性和隔离级别

Spring事务的传播特性和隔离级别

Spring中五个事务的隔离级别和七种传播行为

Spring事务传播属性和隔离级别