Spring 事务浅析

183 阅读9分钟

事务

什么是事务?

事务就像是一个“打包”的操作,它确保一组操作要么全部成功,要么全部失败。你可以把事务想象成一个“全有或全无”的承诺。

举个例子

假设你去银行转账,从你的账户转 200 元到朋友的账户。这个过程可以分为两个步骤:

  1. 从你的账户扣除 200 元。
  2. 向你朋友的账户增加 200 元。

这两个步骤必须要么都成功,要么都失败。不能出现只扣了你的钱但没有加到你朋友账户上的情况。

有了事务,你就可以进行约束,整个过程就是一件事,要么都做成,要么都做不成。它是一个完整的事件,大部分情况下,符合现实世界的认知标准。

事务就是用来保证这种一致性,让事情符合我们的认知逻辑。

事务的特性

事务特性:ACID

原子性 事务是数据库的逻辑工作单位,事务中包括的操作要么全成功,要么全失败。

一致性 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。符合我们现实世界的认知语义。

隔离性 一个事务的执行不能被其他事务干扰。互不干扰。

持久性 一个事务一旦提交,它对数据库的修改是永久的。做完的事情,不能凭空变了、消失了,仍然符合现实生活的语义(不考虑物理损坏)

数据库事务与Spring事务

一件事做成与否,最终都要记入档案,或传统的纸质载体、或现在的硬盘等载体。而我们操作硬盘等新型载体的方式,我们有一个名词叫 数据库

因此,一般情况下,我们指的事务,就是指的数据库事务。为统一各家数据库厂商对数据库的事务语义,先定义统一标准(数据库事务的标准由数据库管理系统(DBMS)定义),各厂商按照标准去实现它。

而 Spring 事务又是什么?

Spring 作为应用层的框架,其出发点都是从软件开发者的角度软件,提升开发效率,如果没有 Spring 事务,我们得熟悉各数据库(Mysql、Oracle ...)事务的控制方式:事务的开启、事务的异常处理、事务的提交 ...

Spring 想到了这里,它抽象了这类通用逻辑,使得我们在 Spring 的环境下,可以更加方便的使用 Spring 包装的事务来控制我们的业务逻辑,尽可能的让我们只需关注业务即可。

简言之:Spring 框架通过抽象和封装这些标准,提供了一套统一的事务管理机制,使得开发者可以方便地控制事务的语义。

Spring 事务

基本使用

在 Spring 的环境中,我们可以非常方便的使用声明式事务(注解),来控制我们的业务:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyService {

    @Autowired
    private MyRepository myRepository;

    @Transactional
    public void performTransactionalOperation() {
        // 业务逻辑
        myRepository.save(new MyEntity());
        
        // 其他数据库操作
    }
}

@Transactional 注解用于声明 performTransactionalOperation 方法是一个事务性方法。

Spring 会自动管理该方法的事务边界,确保在方法执行过程中,所有的数据库操作要么全部成功,要么全部回滚。

在以上整个 @Transactional 标注的方法为一个完整的事务,不管你在该方法中对数据库进行了多少次操作,它都属于一个事务管辖范围内。

那你可能会问,很多方法相互调用,每个方法都被 @Transactional 标注,这些事务边界呢?

这就是事务传播行为。Spring 为此定义了多种传播行为,以应对各种的业务场景。

基本使用如:

@Transactional(propagation = Propagation.REQUIRED)
public void performTransactionalOperation() {
    // 业务逻辑
}

Spring 事务的传播行为

常见的传播行为包括:

  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则将当前事务挂起。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  • NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则将当前事务挂起。
  • NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个嵌套事务(仅适用于特定的事务管理器,如 DataSourceTransactionManager)

默认的传播行为是 Propagation.REQUIRED。

Propagation.REQUIRED 是Spring事务管理的默认传播行为。它的含义是:

  • 如果当前已经存在一个事务,则加入该事务。
  • 如果当前没有事务,则创建一个新的事务。

这种传播行为确保了事务的一致性和原子性,适用于大多数场景。

事务失效

1. 事务方法被final 或 static 关键字修饰

如果一个方法被声明为 final 或者 static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致 Spring 无法生成事务代理对象来管理事务。

2. 同一个类中,进行方法内部调用

Spring AOP 自调用问题

使用 @Transactional注解的声明式事务,Spring 基于 AOP 机制实现事务的管理。

只有通过注入的 Bean 调用事务方法,才会走代理类,才会执行事务管理;如果在同类直接调用,没走代理类,事务就无效。

(注: @Async 同样需要代理类调用,异步才会生效,问题原因相同

当然,你也可以在该类中 注入自己 来解决。

3. 子类重写父类方法进行 super 调用时

父类有一个被 @Transactional 注解标注的方法,子类重写了这个方法并且 @Transactional 注解中的属性值 propagation 与父类不一致。

此时由于子类方法中的注解覆盖了父类的注解,Spring 框架将不会在父类的方法中启动事务,如果进行 super 调用事务会失效。

解决:在子类中将事务注解的属性值与父类保持一致。

4. 在事务中进行多线程调用

Spring 的事务是通过 ThreadLocal 存储的,事务和当前线程绑定,体现在 spring 事务管理器中是这样一个链路:

  1. 通过 TransactionSynchronizationManager 类来管理事务上下文(一个数据源与数据库连接的MAP)。
  2. TransactionSynchronizationManager 内部维护了一个 ThreadLocal 对象,用来存储当前线程的事务上下文。
  3. 在事务开始时,TransactionSynchronizationManager 会将事务上下文绑定到当前线程的 ThreadLocal 对象中。
  4. 当事务结束时,TransactionSynchronizationManager 会将事务上下文从 ThreadLocal 对象中移除。

如果开一个新线程,相当于重新起了一份事务上下文,建立了新的数据库连接,也就是相当于新开了一个事务,自然不会受到原事务的影响。

因此,注意多线程调用的部分是否需要跟原事务一起回滚,需要则最好不要多线程调用。

Spring事务原理

Spring 事务管理通过 AOP 代理和事务管理器实现了 声明式编程式 两种事务管理方式。

声明式事务管理通过 @Transactional 注解和 AOP 代理实现,简化了事务管理的复杂性;编程式事务管理则提供了更细粒度的控制。

声明式事务

1. @Transaction注解

@Transactional 注解可以应用于类或方法上,用于声明该类或方法是事务性的。

Spring 会根据注解的属性配置事务的传播行为、隔离级别、超时时间等。

image.png

2. AOP 代理

Spring使用AOP代理来拦截带有@Transactional注解的方法调用。在方法调用前,代理会启动一个事务;在方法调用后,根据方法执行结果决定是提交还是回滚事务。

  • JDK动态代理:用于代理实现了接口的类。
  • CGLIB代理:用于代理没有实现接口的类

在 Spring 中,对于事务的 AOP 注入管理主要基于 TransactionInterceptor 事务拦截器,其引用了 SpringAOP 模块中对应目标方法前后都进行代理增强的 org.aopalliance.intercept.MethodInterceptor 方法。

同时继承自 Spring 事务管理的抽象基类 TransactionAspectSupport 在这个基类里定义了事务处理的模板方法,以及对应的事务管理器。

3. 事务管理器

Spring 通过 PlatformTransactionManager 接口来抽象事务管理。常见的实现包括:

  • DataSourceTransactionManager:用于 JDBC。
  • JpaTransactionManager:用于 JPA。
  • HibernateTransactionManager:用于 Hibernate。

事务管理器负责具体的事务操作,如开始、提交和回滚事务。

4. 事务执行流程

  1. 方法调用:当调用一个带有 @Transactional 注解的方法时,代理对象会拦截该调用。
  2. 事务管理器:代理对象通过事务管理器来管理事务。
  3. 事务开始:在方法执行前,代理对象会通过事务管理器启动一个事务。
  4. 方法执行:代理对象调用目标方法。
  5. 事务提交或回滚:根据方法执行结果,代理对象决定是提交还是回滚事务。如果方法执行成功,事务管理器会提交事务;如果方法抛出异常,事务管理器会回滚事务。

编程式事务管理

编程式事务管理需要开发者显式地管理事务,使用 TransactionTemplate 或直接使用PlatformTransactionManager。

 1. 使用 TransactionTemplate

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void performTransactionalOperation() {
        transactionTemplate.execute(status -> {
            // 业务逻辑
            myRepository.save(new MyEntity());
            
            // 其他数据库操作
            return null;
        });
    }
}

2. 使用 PlatformTransactionManager

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Service
public class MyService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void performTransactionalOperation() {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setName("myTransaction");
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // 业务逻辑
            myRepository.save(new MyEntity());
            
            // 其他数据库操作

            transactionManager.commit(status);
        } catch (Exception ex) {
            transactionManager.rollback(status);
            throw ex;
        }
    }
}

编程式事务控制需要你显示控制事务开启、提交、回滚...,控制粒度更细,更加灵活。