Spring 事务

一. 代码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    // transactionManager() 的别名,用于指定事务管理器的名称。可以通过指定事务管理器的名称来指定要使用的特定事务管理器。
    @AliasFor("transactionManager")
    String value() default "";

    // 用于指定事务管理器的名称。可以通过指定事务管理器的名称来指定要使用的特定事务管理器。
    @AliasFor("value")
    String transactionManager() default "";

    // 指定事务的传播行为。它定义了事务方法被嵌套调用时的事务传播规则。
    Propagation propagation() default Propagation.REQUIRED;

    // 指定事务的隔离级别。它定义了事务方法对数据的访问和锁定的级别。
    Isolation isolation() default Isolation.DEFAULT;

    // 指定事务的超时时间(以秒为单位)。如果事务在超过指定时间时仍未提交,则将自动回滚事务。
    int timeout() default -1;

    // 指定事务是否为只读事务。如果设置为 true,则表示事务只读,不会修改数据库的内容。
    boolean readOnly() default false;
    
    // 指定在遇到指定的异常时需要回滚事务。可以指定一个或多个异常类型。
    Class<? extends Throwable>[] rollbackFor() default {};

    // 与 rollbackFor() 类似,但以字符串数组的形式指定异常类的全限定名。
    String[] rollbackForClassName() default {};

    // 指定在遇到指定的异常时不需要回滚事务。可以指定一个或多个异常类型。
    Class<? extends Throwable>[] noRollbackFor() default {};

    // 与 noRollbackFor() 类似,但以字符串数组的形式指定异常类的全限定名。
    String[] noRollbackForClassName() default {};
}

二. 事务的传播行为

Spring框架提供了事务管理功能,其中事务的传播机制定义了在方法调用链中嵌套事务的行为。事务传播机制用于控制事务的边界和行为,以确保数据的一致性和完整性。

声明式事务的传播行为可以通过 @Transactional 注解中的 propagation 属性来定义

1.REQUIRED

如果调用方存在事务,则被调用方加入到该事务中;如果调用方没有事务,则被调用方创建一个新的事务。

1.1 方法 A B 均有注解, A 调用 B 时, B 中抛异常

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testB(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

当方法B抛出异常后, 方法A和B的事务均回滚

1.2 方法 A 有注解, 方法 B 无注解, A 调用 B 时, B 中抛异常

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testB(record);
    }

    @Override
    // @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

当方法 B 抛出异常后, 方法 A 和 B 的事务均回滚

1.3 方法 A 无注解, 方法 B 有注解, A 调用 B 时, B 中抛异常

1.3.1 自调用方式
    @Override
    // @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testB(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
        record.setValue("小黄");
        testTableMapper.insert(record);
    }

方法 A 和 B 均没有回滚失效, 数据库中插入了 小明和小红, 没有插入小黄

1.3.2 Spring 代理对象调用
@Service
public class TestServiceImpl implements ITestService {

    @Autowired
    private ITestService testService;

    @Autowired
    TestTableMapper testTableMapper;
    
    @Override
    // @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testService.testB(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
        record.setValue("小黄");
        testTableMapper.insert(record);
    }

}

方法 A 没有回滚, 数据库中插入了小明, 方法 B 事务进行了回滚, 数据库中没有插入小红和小黄

1.4 方法 A B 均有注解, A 调用 B 时, A 中抛异常

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testB(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
    }

方法 A 和 B 均回滚, 数据库中没有插入小明和小红

1.5 方法 A 有注解, 方法 B 无注解, A 调用 B 时, A 中抛异常

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testB(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

    @Override
//    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
    }

方法 A 和 B 均回滚, 数据库中没有插入小明和小红

1.6 方法 A 无注解, 方法 B 有注解, A 调用 B 时, A 中抛异常

1.6.1 自调用
    @Override
//    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testB(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
    }

当 A 抛出异常时, 方法 A 和 B 均没回滚, 数据库插入了小明和小红

1.6.2 Spring 代理对象调用
@Service
public class TestServiceImpl implements ITestService {

    @Autowired
    private ITestService testService;

    @Autowired
    TestTableMapper testTableMapper;

    @Override
//    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testService.testB(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
    }
}

当 A 抛出异常时, 方法 A 和 B 均没回滚, 数据库插入了小明和小红

1.7 方法 A B 都有注解, 在 Controller 中同时调用, 在 B 中抛异常

    @GetMapping("/test")
	public ResultMap insertTest() {

		TestTable a = new TestTable();

		testService.testA(a);
		testService.testB(a);
		return Result.success("ok");
	}

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("我报错了");
        }
    }

方法 A 没有受到影响, 小明顺利插入, B 事务进行回滚, 小红没有被插入, 所以在 Controller 同时调用两个事务型方法时, 无法保证该接口整体回滚。

总结:  当方法进行调用时, 如果调用方存在事务, 则无论被调用方是否存在事务, 都会将被调用方加入到当前事务中; 如果调用方不存在事务, 被调用方存在事务, 则被调用方会自己开启事务, 调用方不受被调用方的事务影响。并且相互间调用时, 采用自调用的方式可能会使事务失效。

2.SUPPORTS

调用方存在事务, 则被调用方加入到该事务中, 调用方不存在事务, 被调用方也不开启新的事务。这种传播行为适用于希望在事务存在时加入事务,但在非事务环境中也可以执行的情况。

2.1 A 调用 B, B方法中事务设为 SUPPORTS 传播行为

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testService.testB(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

方法 A 和 B 均发生了回滚, 数据库没有插入任何数据

2.2 关闭方法 A 的事务

    @Override
//    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testService.testB(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

方法 A 和 B 都没有发生回滚, 数据库插入了小明和小红, 该示例可以与 1.3.2示例 做对比

3.MANDATORY

如果调用方存在事务,则被调用方加入该事务中;如果调用方没有事务,则抛出异常。

4.REQUIRES_NEW

如果调用方存在事务,则把该事务挂起。被调用方会开启自己的事务,且开启的事务与调用方的事务相互独立,互不干扰, 调用方发生异常后, 被调用方的的事务不受影响, 因为 REQUIRES_NEW 会暂停调用方的事务, 直到被调用方的事务提交或者回滚才恢复执行。

4.1 方法 B 开启 REQUIRES_NEW 传播行为

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testService.testB(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
    }

方法 A 报错后, B 方法正常执行, 数据库插入小红, 而 A 事务回滚, 数据库中没有插入小明

5.NOT_SUPPORTED

以非事务方式执行,如果调用方存在事务,则将该事务挂起。

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        testService.testB(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

方法 B 报错后, 数据库插入小红, 而 A 事务回滚, 数据库中没有插入小明

6.NEVER

以非事务方式运行,如果当前存在事务,则抛出异常。

7.NESTED

它表示当前方法被调用时,如果已经存在一个事务,那么当前方法就会在这个已存在的事务中创建一个嵌套事务;如果当前方法没有事务,则会创建一个新的事务。嵌套事务是相对于外层事务的,嵌套在外层事务内部。嵌套事务可以独立进行提交或回滚,并且嵌套事务的提交不会影响外层事务的提交,但如果外层事务回滚,则嵌套事务也会被回滚。

7.1 方法 A 中捕获 方法 B 的异常

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        try{
            testService.testB(record);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

方法 A 没有受到影响, 方法 B 回滚, 数据库中只插入了小明

三. 其它几种调用形式

1.在本方法中抛出异常并捕获

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        try{
            if (true){
                throw new RuntimeException("报错了");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

事务失效, 数据库中插入小明

2.A 方法捕获 B方法的异常

2.1 自调用方式

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        try{
            testB(record);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

事务均失效, 数据库中插入了小明和小红

这是因为在 Spring 中,事务注解的生效需要满足以下两个条件:

1. 被调用的方法必须是 Spring 管理的 Bean,并且被代理才能使事务注解生效。

2. 事务注解必须是通过方法的公有接口调用,而不是通过内部方法调用,即所谓的“自调用”。

2.2 Spring 代理对象调用

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);
        try{
            testService.testB(record);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

报异常 Transaction rolled back because it has been marked as rollback-only, 这是因为在方法B 中标记了 rollback-only,但是后面的程序执行后又commit事务,所以抛出此异常。然后方法A B 均回滚。

3.多线程间的传递情况

3.1 两个方法都开启事物, B方法报错

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);

        Thread thread = new Thread(() ->
                testService.testB(record));
        thread.start();
        
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

A方法没有回滚, B方法回滚了, 数据库插入了小明而没有小红

3.2 A 方法开启事物, B方法不开启

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);

        Thread thread = new Thread(() ->
                testService.testB(record));
        thread.start();

        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
//    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
        if (true){
            throw new RuntimeException("报错了");
        }
    }

方法 AB 均没有回滚, 数据库中插入了 小明和小红

3.3 方法A开启事物, 方法B 不开启, A中报错

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testA(TestTable record) {
        record.setValue("小明");
        testTableMapper.insert(record);

        Thread thread = new Thread(() ->
                testService.testB(record));
        thread.start();

        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        if (true){
            throw new RuntimeException("报错了");
        }
    }

    @Override
//    @Transactional(rollbackFor = Exception.class)
    public void testB(TestTable record){
        record.setValue("小红");
        testTableMapper.insert(record);
    }

方法A 发生回滚, 方法B 没有, 数据库中只插入了小红

总结: 线程间没有事物的传递

四.事务失效的一些场景

1.事务方法未被Spring管理

如果事务方法所在的类没有注册到Spring IOC容器中,也就是说,事务方法所在类并没有被Spring管理,则Spring事务会失效。

2.方法使用final类型修饰

@Transactional 是使用 Spring AOP 实现的,而 Spring AOP 的底层是通过动态代理实现,在代理类中实现事务功能。如果某个方法用final修饰了,那么就无法在代理类中重写该方法,从而无法添加事务功能。

3.非public修饰的方法

@Transactional 在生成代理时会判断,如果方法为非 public 修饰的方法,则不生成代理对象。

4.同一个类中的方法相互调用

因为Spring的事务管理通常是基于AOP(面向切面编程)实现的,而AOP通过动态代理在运行时织入事务逻辑。当一个带有 @Transactional 注解的方法被调用时,Spring会创建一个事务上下文,并将其绑定到当前线程。如果该方法在执行过程中调用了同一个类中的另一个方法,调用会直接进入该方法,而不会经过AOP代理。由于AOP代理没有介入,事务上下文也就无法传播到被调用的方法,导致事务失效。

为了在同一个类中的方法相互调用时保持事务的有效性,可以使用self-invocation(自我调用)的方式,通过获取Spring容器的ApplicationContext并通过它来获取当前类的代理对象,然后通过代理对象调用方法,这样AOP代理会正确拦截方法调用并应用事务管理。

@Service
public class MyService {

    @Autowired
    private ApplicationContext context;

    @Transactional
    public void methodA() {
        // 此处通过代理对象调用methodB(),事务将保持有效
        MyService proxy = context.getBean(MyService.class);
        proxy.methodB();
    }

    @Transactional
    public void methodB() {
        // 执行逻辑
    }
}

5. 方法的事务传播类型不支持事务

比如 NOT_SUPPORTED、SUPPORTS、NEVER 都可能不会开启事务

6. 异常被catch,并且内部消化沒有抛出

7. 数据库不支持事务

8. 未配置开启事务

Springboot 有时候会帮我们进行自动配置事务管理, 但是如果没有的话, 需要手动进行配置。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
public class TransactionConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }
}

9. 多线程调用

事务不会在线程间传递

五. Isolation 事物隔离级别

1. DEFAULT(-1):表示使用默认的事务隔离级别。实际的默认隔离级别取决于数据库管理系统的配置。

2. READ_UNCOMMITTED(1):读未提交, 表示最低的事务隔离级别。在这个隔离级别下,一个事务对数据的修改即使未提交,对其他事务也是可见的。这样的隔离级别可能导致脏读、不可重复读和幻读问题。

3. READ_COMMITTED(2):读已提交, 表示在一个事务提交后,其他事务才能看到这个事务对数据的修改。这样的隔离级别可以避免脏读问题,但可能导致不可重复读和幻读问题。

4. REPEATABLE_READ(4):可重复读, 表示在一个事务执行期间,其他事务不能对它所读取的数据进行修改。这样的隔离级别可以避免脏读和不可重复读问题,但可能导致幻读问题。

5. SERIALIZABLE(8):串行化, 表示最高的事务隔离级别。在这个隔离级别下,一个事务在执行期间完全串行化执行,其他事务不能对它所读取的数据进行修改。这样的隔离级别可以避免脏读、不可重复读和幻读问题,但可能降低并发性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯洁的小魔鬼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值