啃完这篇Spring事务管理+事物隔离级别就够了

Spring事务

事务是逻辑上的一组操做。组成这组操做的各个逻辑单元必须所有完成,若是有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过同样。 java

事务有四个特性:ACID

  1. 原子性(Atomicity):事务是一个原子操做,由一系列动做组成。事务的原子性确保动做要么所有完成,要么彻底不起做用。
  2. 一致性(Consistency):一旦事务完成(无论成功仍是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不该该被破坏。
  3. 隔离性(Isolation):可能有许多事务会同时处理相同的数据,所以每一个事务都应该与其余事务隔离开来,防止数据损坏。
  4. 持久性(Durability):一旦事务完成,不管发生什么系统错误,它的结果都不该该受到影响,这样就能从任何系统崩溃中恢复过来。一般状况下,事务的结果被写到持久化存储器中。

1 传播行为

事务的第一个方面是传播行为(propagation behavior)。当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在本身的事务中运行。Spring定义了七种传播行为:mysql

传播行为 含义
PROPAGATION_REQUIRED 表示当前方法必须运行在事务中。若是当前事务存在,方法将会在该事务中运行。不然,会启动一个新的事务
PROPAGATION_SUPPORTS 表示当前方法不须要事务上下文,可是若是存在当前事务的话,那么该方法会在这个事务中运行
PROPAGATION_MANDATORY 表示该方法必须在事务中运行,若是当前事务不存在,则会抛出一个异常
PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它本身的事务中。一个新的事务将被启动。若是存在当前事务,在该方法执行期间,当前事务会被挂起。若是使用JTATransactionManager的话,则须要访问TransactionManager
PROPAGATION_NOT_SUPPORTED 表示该方法不该该运行在事务中。若是存在当前事务,在该方法运行期间,当前事务将被挂起。若是使用JTATransactionManager的话,则须要访问TransactionManager
PROPAGATION_NEVER 表示当前方法不该该运行在事务上下文中。若是当前正有一个事务在运行,则会抛出异常
PROPAGATION_NESTED 表示若是当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务能够独立于当前事务进行单独地提交或回滚。若是当前事务不存在,那么其行为与PROPAGATION_REQUIRED同样。注意各厂商对这种传播行为的支持是有所差别的。能够参考资源管理器的文档来确认它们是否支持嵌套事务
(下面是通俗讲解)
事务方法被另外一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在本身的事务中运行。Spring定义了七种传播行为:

1》PROPAGATION_REQUIRED 若是存在一个事务,则支持当前事务。若是没有事务则开启一个新的事务。sql

2》PROPAGATION_SUPPORTS 若是存在一个事务,支持当前事务。若是没有事务,则非事务的执行。可是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少量不一样。数据库

3》PROPAGATION_MANDATORY 若是已经存在一个事务,支持当前事务。若是没有一个活动的事务,则抛出异常。编程

4》PROPAGATION_REQUIRES_NEW 老是开启一个新的事务。若是一个事务已经存在,则将这个存在的事务挂起。后端

我把ts1称为外层事务,ts2称为内层事务。从上面的代码能够看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。若是methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所作的结果依然被提交。而除了 methodB以外的其它代码致使的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,须要使用JtaTransactionManager做为事务管理器。并发

5》PROPAGATION_NOT_SUPPORTED 老是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也须要使用JtaTransactionManager做为事务管理器。学习

6》PROPAGATION_NEVER 老是非事务地执行。表示当前方法不该该运行在事务上下文中。若是当前正有一个事务在运行,则会抛出异常。(当事务方法被另外一个事务方法调用时)若是存在一个活动事务,则抛出异常。优化

7》PROPAGATION_NESTED若是一个活动的事务存在,则运行在一个嵌套的事务中. 若是没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。.net

这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager做为事务管理器。须要JDBC 驱动的java.sql.Savepoint类。

有一些JTA的事务管理器实现可能也提供了一样的功能。使用PROPAGATION_NESTED,还须要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而 nestedTransactionAllowed属性值默认为false。

嵌套事务一个很是重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所作的动做。而内层事务操做失败并不会引发外层事务的回滚。

注意:

1》PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们很是相似,都像一个嵌套事务,若是不存在一个活动的事务,都会开启一个新的事务。使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务同样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它须要JTA事务管理器的支持。

2》使用PROPAGATION_NESTED时,外层事务的回滚能够引发内层事务的回滚。而内层事务的异常并不会致使外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,须要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不一样的支持方式。

3》PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被彻底 commited 或 rolled back 而不依赖于外部事务, 它拥有本身的隔离范围, 本身的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。

4》另外一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 若是这个嵌套事务失败, 咱们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。

5》因而可知, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 彻底是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 若是外部事务 commit, 嵌套事务也会被 commit, 这个规则一样适用于 roll back.

6》PROPAGATION_REQUIRED应该是咱们首先的事务传播行为。它可以知足咱们大多数的事务需求。

2 隔离级别

事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其余并发事务 影响的程度。 

并发事务引发的问题

在典型的应用程序中,多个事务并发运行,常常会操做相同的数据来完成各自的任务。并发虽然是必须的,但可能会致使如下的问题:

  1. 脏读(Dirty reads)——脏读发生在一个事务读取了另外一个事务改写但还没有提交的数据时。若是改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
  1. 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,可是每次都获得不一样的数据时。这一般是由于另外一个并发事务在两次查询期间进行了更新
  1. 幻读(Phantom read)——幻读与不可重复读相似。它发生在一个事务(T1)读取了几行数据,接着另外一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些本来不存在的记录

不可重复读与幻读的区别

  1. 不可重复读的重点是修改:一样的条件, 你读取过的数据, 再次读取出来发现值不同了 
  2. 幻读的重点在于新增或者删除:一样的条件, 第1次和第2次读出来的记录数不同
  3. 从总的结果来看, 彷佛不可重复读和幻读都表现为两次读取的结果不一致。但若是你从控制的角度来看, 二者的区别就比较大。 对于前者, 只须要锁住知足条件的记录。 对于后者, 要锁住知足条件及其相近的记录
隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 最低的隔离级别,容许读取还没有提交的数据变动,可能会致使脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 容许读取并发事务已经提交的数据,能够阻止脏读,可是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ 对同一字段的屡次读取结果都是一致的,除非数据是被自己事务本身所修改,能够阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE 最高的隔离级别,彻底服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,由于它一般是经过彻底锁定事务相关的数据库表来实现的
对上表的一些解释以下:

1》ISOLATION_DEFAULT  使用后端数据库默认的隔离级别。(ISOLATION [ˌaɪsəˈleɪʃn] 隔离)

2》ISOLATION_READ_UNCOMMITTED 最低的隔离级别,容许读取还没有提交的数据变动,可能会致使脏读、幻读或不可重复读

3》ISOLATION_READ_COMMITTED 容许读取并发事务已经提交的数据,能够阻止脏读,可是幻读不可重复读仍有可能发生。(脏读:发生在  一个事务读取了,另外一个事务改写了但还没有提交到数据库的数据时。由于改写没有提交到数据库,因此可能会回滚。致使第一个事务读到的值无效)

4》ISOLATION_REPEATABLE_READ 一个事务中,对同一字段的屡次读取结果都是一致的,除非数据是被自己事务本身所修改。能够阻止脏读和不可重复读,但幻读仍有可能发生不可重复读:一个事务中执行屡次相同的查询时,读取到的值不一样,重点在于修改update

5》ISOLATION_SERIALIZABLE   最高的隔离级别,彻底服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,由于它一般是经过彻底锁定事务相关的数据库表来实现的。(幻读:一个事务中,2次或屡次执行相同的查询时,获得的结果的行数不同。重点在于增长insert和删除delete)

3 只读

事务的第三个特性是它是否为只读事务。若是事务只对后端的数据库进行该操做,数据库能够利用事务的只读特性来进行一些特定的优化。经过将事务设置为只读,你就能够给数据库一个机会,让它应用它认为合适的优化措施。

4 事务超时

为了使应用程序很好地运行,事务不能运行太长的时间。由于事务可能涉及对后端数据库的锁定,因此长时间的事务 会没必要要地占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务若是没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

5 回滚规则

事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会致使事务回滚,而哪些不会。默认状况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)  可是你能够声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。一样,你还能够声明事务遇到特定的异常不回滚,即便这些异常是运行期异常。

编程式事务

Spring提供了对编程式事务和声明式事务的支持。编程式事务容许用户在代码中精肯定义事务的边界,而声明式事务(基于AOP)有助于用户将操做与事务规则进行解耦。 

简单地说,编程式事务侵入到了业务代码里面,可是提供了更加详细的事务管理;而声明式事务因为基于AOP,因此既能起到事务管理的做用,又能够不影响业务代码的具体实现。(编程式事务放到了代码里。声明式事务放在AOP里面)

看到这里别忘记点个小小的赞喔~ 更多往期文章我已整合成PDF放在了个人社区,也整理了一些Java学习的zl,须要的小伙伴点击传送门

相关文章
相关标签/搜索