Java 事务 Spring

数据库的事务

构成单一逻辑工作单元的操作集合称作事务(transaction)。即使有故障,数据库系统也必须保证事务的正确执行——要么执行整个事务,要么属于该事务的操作一个也不执行。以资金转账为例,应该保证支票账户支出金额的操作和储蓄账户的存入金额的操作在同一个逻辑工作单元内完成。简言之,事务是访问并可能更新各种数据项的一个程序执行单元(unit)
数据库事务(Transaction,简写为 TX)是数据库管理系统执行过程中的一个逻辑单位,是可以提交或回滚的工作的原子单元。当事务对数据库进行多次更改时,要么在提交事务时所有更改都成功,要么在回滚事务时所有更改都被撤消。
数据库(包括但不限于关系型)事务一般拥有以下 4 个特性,称之为 ACID 特性

ACID

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

    事务的隔离级别

    在实际应用中,数据库中的数据是要被多个用户共同访问的,在多个用户同时操作相同的数据时,可能就会出现一些事务并发的问题:
  1. 脏读(Dirty Read)。一个事务读取到另一个事务未提交的数据。
  2. 不可重复读(Non-repeatable Read)。一个事务对同一行数据重复读取两次,但得到的结果不同。
  3. 虚读/幻读(Phantom Read)。一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。
  4. 丢失更新(Lost Update)。丢失更新可分为两类,分别是第一类丢失更新和第二类丢失更新。第一类丢失更新是指两个事务同时操作同一个数据时,当第一个事务撤销时,把已经提交的第二个事务的更新数据覆盖了,第二个事务就造成了数据丢失。第二类丢失更新是指当两个事务同时操作同一个数据时,第一个事务将修改结果成功提交后,对第二个事务已经提交的修改结果进行了覆盖,对第二个事务造成了数据丢失。

为了避免上述事务并发问题的出现,在标准的 SQL 规范中定义了四种事务隔离级别,不同的隔离级别对事务的处理有所不同:

  1. Serializable(可串行化)提供严格的事务隔离。它要求事务序列化执行,事务只能一个接一个地执行,不能并发执行。此隔离级别可有效防止脏读、不可重复读、幻读。但这个级别可能导致大量的超时现象和锁竞争,在实际应用中很少使用。
  2. Repeatable Read(可重复读)一个事务在执行过程中,可以访问其他事务成功提交的新插入的数据,但不可以访问成功修改的数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。此隔离级别可有效防止不可重复读和脏读。
  3. Committed Read(已提交读)一个事务在执行过程中,既可以访问其他事务成功提交的新插入的数据,又可以访问成功修改的数据。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。此隔离级别可有效防止脏读。
  4. Uncommitted Read(未提交读)一个事务在执行过程中,既可以访问其他事务未提交的新插入的数据,又可以访问未提交的修改数据。如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。此隔离级别可防止丢失更新。

以上所有隔离性级别都不允许脏写(Dirty Write)。
一般来说,事务的隔离级别越高,越能保证数据库的完整性一致性,但相对来说,隔离级别越高,对并发性能的影响也越大。因此,通常将数据库的默认隔离级别设置为已提交读 (Committed Read),它既能防止脏读,又能有较好的并发性能。虽然这种隔离级别会导致不可重复读、幻读和第二类丢失更新这些并发问题,但可通过在应用程序中采用悲观锁或乐观锁加以控制。

事务的七种传播行为

什么是事务的传播行为:事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

  • PROPAGATION_REQUIRED 表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。( 如果被调用端发生异常,那么调用端和被调用端事务都将回滚)
  • PROPAGATION_SUPPORTS 表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行
  • PROPAGATION_MANDATORY 表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常
  • PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
  • PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。
  • PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常
  • PROPAGATION_NESTED 表示如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同propagation.required的一样

    MySQL 中的事务

    ```plsql START TRANSACTION [transaction_characteristic [, transaction_characteristic] …]

transaction_characteristic: { WITH CONSISTENT SNAPSHOT | READ WRITE | READ ONLY }

BEGIN [WORK] COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE] SET autocommit = {0 | 1}

  1. - `START TRANSACTION` `BEGIN`开始新事务。
  2. - `COMMIT` 提交当前事务。
  3. - `ROLLBACK` 回滚当前事务。
  4. - `SET autocommit` 禁用或启用当前会话的默认自动提交模式。
  5. 默认情况下,MySQL是自动提交的模式,所有语句会立即提交
  6. <a name="zQb4w"></a>
  7. ## JDBC 中的事务
  8. JDBC Java 语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了查询和更新数据库中数据的方法。JDBC 也是 Sun Microsystems 的商标(现在属于 Oracle),是面向关系型数据库的。<br />上面说到,MySQL 是默认自动提交的,所以 JDBC 中事务事务的第一步,需要禁用自动提交:
  9. ```java
  10. con.setAutoCommit(false);

提交事务:

  1. con.commit();

回滚事务:

  1. con.rollback();

一个完整流程的例子(摘自 Oracle JDBC 文档):

  1. public void updateCoffeeSales(HashMap<String, Integer> salesForWeek)
  2. throws SQLException {
  3. PreparedStatement updateSales = null;
  4. PreparedStatement updateTotal = null;
  5. String updateString =
  6. "update " + dbName + ".COFFEES " +
  7. "set SALES = ? where COF_NAME = ?";
  8. String updateStatement =
  9. "update " + dbName + ".COFFEES " +
  10. "set TOTAL = TOTAL + ? " +
  11. "where COF_NAME = ?";
  12. try {
  13. con.setAutoCommit(false);
  14. updateSales = con.prepareStatement(updateString);
  15. updateTotal = con.prepareStatement(updateStatement);
  16. for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
  17. updateSales.setInt(1, e.getValue().intValue());
  18. updateSales.setString(2, e.getKey());
  19. updateSales.executeUpdate();
  20. updateTotal.setInt(1, e.getValue().intValue());
  21. updateTotal.setString(2, e.getKey());
  22. updateTotal.executeUpdate();
  23. con.commit();
  24. }
  25. } catch (SQLException e ) {
  26. JDBCTutorialUtilities.printSQLException(e);
  27. if (con != null) {
  28. try {
  29. System.err.print("Transaction is being rolled back");
  30. con.rollback();
  31. } catch(SQLException excep) {
  32. JDBCTutorialUtilities.printSQLException(excep);
  33. }
  34. }
  35. } finally {
  36. if (updateSales != null) {
  37. updateSales.close();
  38. }
  39. if (updateTotal != null) {
  40. updateTotal.close();
  41. }
  42. con.setAutoCommit(true);
  43. }
  44. }
  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. public class DemoApplication {
  4. public static void main(String[] args) {
  5. // 1.获取连接
  6. Connection conn = DriverManager.getConnection();
  7. try {
  8. // 2.将自动提交设置为false
  9. conn.setAutoCommit(false);
  10. /*-----------------*/
  11. // 3.执行一到多个CRUD操作
  12. /*-----------------*/
  13. // 4.1手动提交
  14. conn.commit();
  15. } catch (Exception e) {
  16. // 4.2一旦其中一个操作出错都将回滚,所有操作都不成功
  17. conn.rollback();
  18. } finally {
  19. // 5.关闭连接
  20. conn.colse();
  21. }
  22. }
  23. }

为什么需要事务管理器

如果没有事务管理器的话,程序可能是这样:

  1. Connection connection = acquireConnection();
  2. try{
  3. int updated = connection.prepareStatement().executeUpdate();
  4. connection.commit();
  5. }catch (Exception e){
  6. rollback(connection);
  7. }finally {
  8. releaseConnection(connection);
  9. }

也有可能是这样 “优雅的事务”:

  1. execute(new TxCallback() {
  2. @Override
  3. public Object doInTx(Connection var1) {
  4. //do something...
  5. return null;
  6. }
  7. });
  8. public void execute(TxCallback txCallback){
  9. Connection connection = acquireConnection();
  10. try{
  11. txCallback.doInTx(connection);
  12. connection.commit();
  13. }catch (Exception e){
  14. rollback(connection);
  15. }finally {
  16. releaseConnection(connection);
  17. }
  18. }
  19. # lambda
  20. execute(connection -> {
  21. //do something...
  22. return null;
  23. });

但是以上两种方式,针对一些复杂的场景是很不方便的。在实际的业务场景中,往往有比较复杂的业务逻辑,代码冗长,逻辑关联复杂,如果一个大操作中又全是这种代码的话开发人员可能会疯。更不用提定制化的隔离级别,以及嵌套 / 独立事务的处理了。

Spring 事务管理器(Transaction Manager)简介

Spring 作为 Java 最强框架,事务管理也是其核心功能之一。Spring 为事务管理提供了统一的抽象,有以下优点:

  • 跨不同事务 API(例如 Java 事务 API(JTA),JDBC,Hibernate,Java 持久性 API(JPA)和 Java 数据对象(JDO))的一致编程模型。
  • 支持声明式事务管理(注解形式)
  • 与 JTA 之类的复杂事务 API 相比, 用于程序化事务管理的 API 更简单
  • 和 Spring 的 Data 层抽象集成方便(比如 Spring - Hibernate/Jdbc/Mybatis/Jpa…)

Spring都使用统一的编程模型,使得应用程序可以很容易地在不同的事务框架之间进行切换。这也符合面向接口编程思想。Spring事务框架的代码在org.springframework:spring-tx中。Spring事务抽象的核心类图如下:
2021-05-12-09-14-54-850013.png
Spring 的事务管理器只是一个接口 / 抽象,不同的 DB 层框架(其实不光是 DB 类框架,支持事务模型的理论上都可以使用这套抽象) 可能都需要实现此标准才可以更好的工作,核心接口是org.springframework.transaction.support.AbstractPlatformTransactionManager,其代码位于spring-tx模块中,比如 Hibernate 中的实现为:org.springframework.orm.hibernate4.HibernateTransactionManager
接口PlatformTransactionManager定义事务操作的行为,PlatformTransactionManager依赖TransactionDefinitionTransactionStatus接口。TransactionDefinition接口定义与Spring兼容的事务属性(如隔离级别、事务传播行为等)。TransactionStatus接口则定义事务的状态(如是否回滚、是否完成、是否包含安全点(Save Point)、将基础会话刷新到数据存储区(如果适用)等)。

PlatformTransactionManager简介

PlatformTransactionManager是Spring事务框架的核心接口。应用程序可以直接使用PlatformTransactionManager,但它并不是主要用于API:应用程序将借助事务模板(TransactionTemplate)或声明式事务(Declarative Transaction)。对于需要实现PlatformTransactionManager接口的应用程序,可通过继承AbstractPlatformTransactionManager抽象类的方式实现。AbstractPlatformTransactionManager类已实现事务传播行为和事务同步处理。子类需要实现针对事务特定状态(如:begin,suspend,resume,commit)的模板方法。Spring事务框架已经实现了JtaTransactionManager(JPA)和DataSourceTransactionManager(JDBC)。应用程序可以参考以上方法实现事务管理器。PlatformTransactionManager事务继承示例如下:
2021-05-12-09-14-54-962520.png

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

TransactionDefinition接口中定义了Spring事务隔离级别和Spring事务传播级别。隔离级别主要控制事务并发访问时隔离程度。Spring支持的隔离级别如下:

隔离级别 描述
DEFAULT 使用数据库本身使用的隔离级别ORACLE(读已提交)MySQL(可重复读)
READ_UNCOMITTED 读未提交(脏读)最低的隔离级别,一切皆有可能。
READ_COMMITED 读已提交,ORACLE默认隔离级别,有幻读以及不可重复读风险。
REPEATABLE_READ 可重复读,解决不可重复读的隔离级别,但还是有幻读风险。
SERLALIZABLE 串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了

除了使用ISOLATION_DEFAULT表示使用数据库默认的隔离级别外,其余四个隔离级别与数据库规范的隔离级别一致。需要注意的是,隔离级别越高,意味着数据库事务并发执行性能越差。JDBC规范虽然定义了事务支持的以上行为,但是各个JDBC驱动、数据库厂商对事务的支持程度可能各不相同。出于性能的考虑我们一般设置READ_COMMITTED级别。针对READ_COMMITTED隔离级别无法避免的脏读,通常使用数据库的锁来处理。传播级别主要控制含事务方法的调用(如一个事务方法调用另一个事务方法)时,Spring对事务的处理方式。Spring事务传播级别共七类。它们是:
(1)**PROPAGATION_REQUIRED**:支持当前事务,如果当前有事务则加入,如果当前没有事务则新建一个。这种方式是默认的事务传播方式
(2)**PROPAGATION_SUPPORTS**:支持当前事务,如果当前有事务则加入,如果当前没有事务则以非事务方式执行。
(3)**PROPAGATION_MANDATORY**:支持当前事务,如果当前有事务则加入,如果当前没有事务则抛出异常。(当前必须有事务)
(4)**PROPAGATION_REQUIRES_NEW**:支持当前事务,如果当前有事务则挂起当前事务,然后新创建一个事务,如果当前没有事务则自己创建一个事务。
(5)**Propagation_NOT_SUPPORTED**:不支持当前事务,如果当前有事务则把当前事务挂起,执行完后恢复事务(忽略当前事务)。
(6)**PROPAGATION_NEVER**:不支持当前事务,如果当前存在事务,则抛出异常。(当前必须不能有事务)
(7)**PROPAGATION_NESTED**:如果当前存在事务,则嵌套在当前事务中。如果当前没有事务,则新建一个事务自己执行。对嵌套事务来说,内部事务回滚时不会影响外部事务的提交;但是外部事务回滚会把内部事务一起回滚回去。(这个和新建一个事务的区别)

使用方式

事务,自然是控制业务的,在一个业务流程内,往往希望保证原子性,要么全成功要么全失败。所以事务一般是加载@Service层,一个 Service 内调用了多个操作数据库的操作(比如 Dao),在 Service 结束后事务自动提交,如有异常抛出则事务回滚。
这也是 Spring 事务管理的基本使用原则。

注解

在被 Spring 管理的类头上增加@Transactional注解,即可对该类下的所有方法开启事务管理。事务开启后,方法内的操作无需手动开启 / 提交 / 回滚事务,一切交给 Spring 管理即可。

  1. @Service
  2. @Transactional
  3. public class TxTestService{
  4. @Autowired
  5. private OrderRepo orderRepo;
  6. public void submit(Order order){
  7. orderRepo.save(order);
  8. }
  9. }

也可以只在方法上配置,方法配置的优先级是大于类的

  1. @Service
  2. public class TxTestService{
  3. @Autowired
  4. private OrderRepo orderRepo;
  5. @Transactional
  6. public void submit(Order order){
  7. orderRepo.save(order);
  8. }
  9. }

TransactionTemplate

TransactionTemplate 这中方式,其实和使用注解形式的区别不大,其核心功能也是由 TransactionManager 实现的,这里只是换了个入口

  1. public <T> T execute(TransactionCallback<T> action) throws TransactionException {
  2. if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
  3. return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
  4. }
  5. else {
  6. //获取事务信息
  7. TransactionStatus status = this.transactionManager.getTransaction(this);
  8. T result;
  9. try {
  10. //执行业务代码
  11. result = action.doInTransaction(status);
  12. }
  13. //处理异常回滚
  14. catch (RuntimeException ex) {
  15. // Transactional code threw application exception -> rollback
  16. rollbackOnException(status, ex);
  17. throw ex;
  18. }
  19. catch (Error err) {
  20. // Transactional code threw error -> rollback
  21. rollbackOnException(status, err);
  22. throw err;
  23. }
  24. catch (Exception ex) {
  25. // Transactional code threw unexpected exception -> rollback
  26. rollbackOnException(status, ex);
  27. throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
  28. }
  29. //提交事务
  30. this.transactionManager.commit(status);
  31. return result;
  32. }
  33. }

XML 配置 tx:advice

过于古老,不做解释

隔离级别 (Isolation Level)

事务隔离级别是数据库最重要的特性之一,他保证了脏读 / 幻读等问题不会发生。作为一个事务管理框架自然也是支持此配置的,在 @Transactional 注解中有一个 isolation 配置,可以很方便的配置各个事务的隔离级别,等同于connection.setTransactionIsolation()

  1. Isolation {
  2. DEFAULT(-1),
  3. READ_UNCOMMITTED(1),
  4. READ_COMMITTED(2),
  5. REPEATABLE_READ(4),
  6. SERIALIZABLE(8);
  7. }

传播行为 (Propagation behavior)

可能没有接触过 Spring 的人听到传播行为会奇怪,这是个什么东西。
其实这个传播行为和数据库功能无关,只是事务管理器为了处理复杂业务而设计的一个机制。比如现在有这样一个调用场景,A Service -> B Service -> C Service,但是希望 A/B 在一个事务内,C 是一个独立的事务,同时 C 如果出错,不影响 AB 所在的事务。
此时,就可以通过传播行为来处理;将 C Service 的事务配置为@Transactional(propagation = Propagation.REQUIRES_NEW)即可
Spring 支持以下几种传播行为:

  • REQUIRED 默认策略,优先使用当前事务(及当前线程绑定的事务资源),如果不存在事务,则开启新事务
  • SUPPORTS 优先使用当前的事务(及当前线程绑定的事务资源),如果不存在事务,则以无事务方式运行
  • MANDATORY 优先使用当前的事务,如果不存在事务,则抛出异常
  • REQUIRES_NEW 创建一个新事务,如果存在当前事务,则挂起(Suspend)
  • NOT_SUPPORTED 以非事务方式执行,如果当前事务存在,则挂起当前事务。
  • NEVER 以非事务方式执行,如果当前事务存在,则抛出异常

    回滚策略

    @Transactional 中有 4 个配置回滚策略的属性,分为 Rollback 策略,和 NoRollback 策略
    默认情况下,RuntimeExceptionError 这两种异常会导致事务回滚,普通的 Exception(需要 Catch 的)异常不会回滚。
    Rollback
    配置需要回滚的异常类
    1. # 异常类Class
    2. Class<? extends Throwable>[] rollbackFor() default {};
    3. # 异常类ClassName,可以是FullName/SimpleName
    4. String[] rollbackForClassName() default {};
    NoRollback
    针对一些要特殊处理的业务逻辑,比如插一些日志表,或者不重要的业务流程,希望就算出错也不影响事务的提交。
    可以通过配置 NoRollbackFor 来实现,让某些异常不影响事务的状态。
    1. # 异常类Class
    2. Class<? extends Throwable>[] noRollbackFor() default {};
    3. # 异常类ClassName,可以是FullName/SimpleName
    4. String[] noRollbackForClassName() default {};

    只读控制

    设置当时事务的只读标示,等同于connection.setReadOnly()
    关键名词解释
名词 概念
PlatformTransactionManager 事务管理器,管理事务的各生命周期方法,简称TxMgr
TransactionAttribute 事务属性,包含隔离级别,传播行为,是否只读等信息,简称TxAttr
TransactionStatus 事务状态,包含当前事务、挂起等信息,简称TxStatus
Transactionlnfo 事务信息,内含TxMgrTxAttrTxStatus等信息,简称Txlnfo
Transactionsynchronization 事务同步回调,内含多个钩子方法,简称TxSync / transaction synchronization
TransactionSynchronizationManag 事务同步管理器,维护当前线程事务资源,信息以及TxSync 集合

基本原理

  1. public void execute(TxCallback txCallback){
  2. //获取连接
  3. Connection connection = acquireConnection();
  4. try{
  5. //执行业务代码
  6. doInService();
  7. //提交事务
  8. connection.commit();
  9. }catch (Exception e){
  10. //回滚事务
  11. rollback(connection);
  12. }finally {
  13. //释放连接
  14. releaseConnection(connection);
  15. }
  16. }

Spring 事务管理的基本原理就是以上代码,获取连接 -> 执行代码 -> 提交 / 回滚事务。Spring 只是将这个流程给抽象出来了,所有事务相关的操作都交由 TransactionManager 去实现,然后封装一个模板形式的入口来执行 t
比如org.springframework.transaction.support.TransactionTemplate的实现:

  1. @Override
  2. public <T> T execute(TransactionCallback<T> action) throws TransactionException {
  3. if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
  4. return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
  5. }
  6. else {
  7. //通过事务管理器获取事务
  8. TransactionStatus status = this.transactionManager.getTransaction(this);
  9. T result;
  10. try {
  11. //执行业务代码
  12. result = action.doInTransaction(status);
  13. }
  14. //处理异常回滚
  15. catch (RuntimeException ex) {
  16. // Transactional code threw application exception -> rollback
  17. rollbackOnException(status, ex);
  18. throw ex;
  19. }
  20. catch (Error err) {
  21. // Transactional code threw error -> rollback
  22. rollbackOnException(status, err);
  23. throw err;
  24. }
  25. catch (Exception ex) {
  26. // Transactional code threw unexpected exception -> rollback
  27. rollbackOnException(status, ex);
  28. throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
  29. }
  30. //提交事务
  31. this.transactionManager.commit(status);
  32. return result;
  33. }
  34. }

注解形式的事务(@Transactional),实现机制也是一样,基于 Spring 的 AOP,将上面 Template 的模式换成了自动的 AOP,在 AOP 的 Interceptor(org.springframework.transaction.interceptor.TransactionInterceptor)中来执行这套流程:

  1. protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
  2. throws Throwable {
  3. // If the transaction attribute is null, the method is non-transactional.
  4. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
  5. //获取事务管理器
  6. final PlatformTransactionManager tm = determineTransactionManager(txAttr);
  7. final String joinpointIdentification = methodIdentification(method, targetClass);
  8. if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
  9. // Standard transaction demarcation with getTransaction and commit/rollback calls.
  10. //创建事务
  11. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
  12. Object retVal = null;
  13. try {
  14. // This is an around advice: Invoke the next interceptor in the chain.
  15. // This will normally result in a target object being invoked.
  16. //执行被“AOP”的代码
  17. retVal = invocation.proceedWithInvocation();
  18. }
  19. catch (Throwable ex) {
  20. // target invocation exception
  21. //处理异常回滚
  22. completeTransactionAfterThrowing(txInfo, ex);
  23. throw ex;
  24. }
  25. finally {
  26. //清除资源
  27. cleanupTransactionInfo(txInfo);
  28. }
  29. //提交事务
  30. commitTransactionAfterReturning(txInfo);
  31. return retVal;
  32. }
  33. ....
  34. }

复杂流程下的事务传播 / 保持相同事务的关键:

对于复杂一些的业务流程,会出现各种类之间的调用,Spring 是如何做到保持同一个事务的?
其实基本原理很简单,只需要将当前事务(Connection)隐式的保存至事务管理器内,后续方法在执行 JDBC 操作前,从事务管理器内获取即可:
比如HibernateTemplate中的SessionFactory中的getCurrentSession,这里的getCurrentSession就是从(可能是间接的)Spring 事务管理器中获取的 Spring 事务管理器将处理事务时的相关临时资源(Connection 等)存在org.springframework.transaction.support.TransactionSynchronizationManager中,通过 ThreadLocal 维护

  1. public abstract class TransactionSynchronizationManager {
  2. private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
  3. private static final ThreadLocal<Map<Object, Object>> resources =
  4. new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
  5. private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
  6. new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
  7. private static final ThreadLocal<String> currentTransactionName =
  8. new NamedThreadLocal<String>("Current transaction name");
  9. private static final ThreadLocal<Boolean> currentTransactionReadOnly =
  10. new NamedThreadLocal<Boolean>("Current transaction read-only status");
  11. private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
  12. new NamedThreadLocal<Integer>("Current transaction isolation level");
  13. private static final ThreadLocal<Boolean> actualTransactionActive =
  14. new NamedThreadLocal<Boolean>("Actual transaction active");
  15. ...
  16. }

针对一些复杂场景,嵌套事务 + 独立事务,涉及到挂起(suspend),恢复(resume)的情况,相关资源也是存储在TransactionSynchronizationManager中的,方便嵌套事务的处理。比如 A->B 时,A 方法已经开启了事务,并将当前事务资源绑定在TransactionSynchronizationManager,那么执行 B 之前,会检测当前是否已经存在事务;检测方式就是从TransactionSynchronizationManager查找并检测状态,如果已经在事务内,那么就根据不同的传播行为配置来执行不同的逻辑,对于 REQUIRES_NEW等传播行为的处理会麻烦一些,会涉及到 “挂起(suspend)” 和恢复 (resume) 的操作,原理打通小异,这里就不做过多解释了

常见问题

事务没生效
有下列代码,入口为 test 方法,在 testTx 方法中配置了 @Transactional 注解,同时在插入数据后抛出 RuntimeException 异常,但是方法执行后插入的数据并没有回滚,竟然插入成功了

  1. public void test(){
  2. testTx();
  3. }
  4. @Transactional
  5. public void testTx(){
  6. UrlMappingEntity urlMappingEntity = new UrlMappingEntity();
  7. urlMappingEntity.setUrl("http://www.baidu.com");
  8. urlMappingEntity.setExpireIn(777l);
  9. urlMappingEntity.setCreateTime(new Date());
  10. urlMappingRepository.save(urlMappingEntity);
  11. if(true){
  12. throw new RuntimeException();
  13. }
  14. }

这里不生效的原因是因为入口的方法 / 类没有增加 @Transaction 注解,由于 Spring 的事务管理器也是基于 AOP 实现的,不管是 Cglib(ASM) 还是 Jdk 的动态代理,本质上也都是子类机制;在同类之间的方法调用会直接调用本类代码,不会执行动态代理曾的代码;所以在这个例子中,由于入口方法test没有增加代理注解,所以textTx方法上增加的事务注解并不会生效

异步后事务失效

比如在一个事务方法中,开启了子线程操作库,那么此时子线程的事务和主线程事务是不同的。
因为在 Spring 的事务管理器中,事务相关的资源(连接,session,事务状态之类)都是存放在 TransactionSynchronizationManager 中的,通过 ThreadLocal 存放,如果跨线程的话就无法保证一个事务了

  1. # TransactionSynchronizationManager.java
  2. private static final ThreadLocal<Map<Object, Object>> resources =
  3. new NamedThreadLocal<>("Transactional resources");
  4. private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
  5. new NamedThreadLocal<>("Transaction synchronizations");
  6. private static final ThreadLocal<String> currentTransactionName =
  7. new NamedThreadLocal<>("Current transaction name");
  8. private static final ThreadLocal<Boolean> currentTransactionReadOnly =
  9. new NamedThreadLocal<>("Current transaction read-only status");
  10. private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
  11. new NamedThreadLocal<>("Current transaction isolation level");
  12. private static final ThreadLocal<Boolean> actualTransactionActive =
  13. new NamedThreadLocal<>("Actual transaction active");

事务提交失败

  1. org.springframework.transaction.UnexpectedRollbackException:
  2. Transaction silently rolled back because it has been marked as rollback-only

这个异常是由于在同一个事务内,多个事务方法之间调用,子方法抛出异常,但又被父方法忽略了导致的。
因为子方法抛出了异常,Spring 事务管理器会将当前事务标为失败状态,准备进行回滚,可是当子方法执行完毕出栈后,父方法又忽略了此异常,待方法执行完毕后正常提交时,事务管理器会检查回滚状态,若有回滚标示则抛出此异常。
具体可以参考org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
示例代码:

  1. A -> B
  2. # A Service(@Transactional):
  3. public void testTx(){
  4. urlMappingRepo.deleteById(98l);
  5. try{
  6. txSubService.testSubTx();
  7. }catch (Exception e){
  8. e.printStackTrace();
  9. }
  10. }
  11. # B Service(@Transactional)
  12. public void testSubTx(){
  13. if(true){
  14. throw new RuntimeException();
  15. }
  16. }