事务注解@Transactional

目录

一、属性介绍

value 和 transactionManager 属性

它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。

isolation 属性

事务的隔离级别,默认值为 Isolation.DEFAULT。可选的值有:

  • Isolation.DEFAULT:使用底层数据库默认的隔离级别
  • Isolation.READ_UNCOMMITTED:读取未提交数据(会出现脏读,不可重复读)基本不使用
  • Isolation.READ_COMMITTED:读取已提交数据(会出现不可重复读和幻读)
  • Isolation.REPEATABLE_READ:可重复读(会出现幻读)
  • Isolation.SERIALIZABLE:串行化

timeout 属性

事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性

指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true

rollbackFor 属性

用于指定能够触发事务回滚的异常类型,可以指定多个异常类型

noRollbackFor 属性

抛出指定的异常类型,不会滚事务,也可以指定多个异常类型

propagation 属性

事务的传播行为,默认值为 Propagation.REQUIRED。可选的值有:

  • PROPAGATION.REQUIRED:如果当前没有事务,则创建一个新事务。如果当前存在事务,就加入该事务。该设置是最常用的设置。
  • PROPAGATION.SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务。如果当前不存在事务,就以非事务执行。
  • PROPAGATION.MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
  • PROPAGATION.REQUIRE_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
  • PROPAGATION.NOT_SUPPORTED:以非事务方式执行操作,如果当前事务存在,就把当前事务挂起。
  • PROPAGATION.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION.NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 REQUIRED 属性执行

二、传播机制

属性上最值得关注的应该就是传播行为了,而在其中最难理解的就是这三个了,毕竟我们最关注的就是两个方法相互调用,发生了异常,是怎么回滚的是吧?其他的4个相对来说比较好理解

  • PROPAGATION.REQUIRED
  • PROPAGATION.REQUIRE_NEW
  • PROPAGATION.NESTED

准备例子

就是两个常规的Service,我们用A 去调用 B, A 方法固定采用REQUIRED ,B方法分别采用REQUIRED/REQUIRED_NEW/NESTED中的一种,然后在两个方法后分别加上异常看看回滚情况

// A Service
@Service
public class TestAImpl implements TestAService {
    @Resource
    private TestBService testBService;
    @Resource
    private TestMapper testMapper;

    @Override
    @Transactional(rollbackFor = Exception.class , propagation = Propagation.REQUIRED)
    public void testA() {
        testMapper.insertData(null,"testA");
        testBService.testB();
    }
}


// B  Service
@Service
public class TestBImpl implements TestBService {
    @Resource
    private TestAService testAService;

    @Resource
    private TestMapper testMapper;

    @Override
    @Transactional(rollbackFor = Exception.class , propagation = Propagation.REQUIRED/REQUIRED_NEW/NESTED)
    public void testB() {
        testMapper.insertData(null,"testB");
    }
}

首先我们要知道A调用B,如果B抛出异常在A中不捕获的话,那A是会正常回滚的,所以在测试REQUIRED_NEW/NESTED的时候记得在A中捕获B的异常

结果:

REQUIRED: 无论A异常还是B异常,双方都会回滚(注意不要捕获异常)

REQUIRED_NEW:因为B是独立的事务,所以A异常:A回滚,B不会回滚;B异常:B回滚,A不会回滚(前提是A要捕获B的异常,否则异常就会自己向上抛,也就影响了A)

NESTED:因为B是嵌套的事务,所以A异常,A和B都会回滚;B异常,B回滚,A不会回滚(前提是A要捕获B的异常,否则异常就会自己向上抛,也就影响了A)

总结

所以综合来看,最容易产生歧义的就是REQUIRED_NEWNESTED,用这两者的时候记得要捕获B的异常,否则异常向上抛就没意义了,而这两个最大的区别就是一个是独立,一个是嵌套独立则是父方法和子方法相互不影响,嵌套则是父方法影响子方法,而子方法不会影响父方法

三、原理

该注解的原理就是AOP,既然是AOP那就是动态代理,与AOP里面其他通知处理方法一样,该注解一样有一个拦截器处理方法,那就是TransactionInterceptor

源代码如下:

所以最终执行的的处理方法就是这个this.invokeWithinTransaction,该方法就是父类的方法,感兴趣的可以自己去看一下:

TransactionAspectSupport.invokeWithinTransaction

AOP最后执行的就是一个拦截器链条嘛。这个事务拦截器也会被放到链条里面去执行,有没有想过AOP里面的方法异常了,事务会回滚吗?

AOP通知方法异常会回滚吗?

正常情况下会回滚,拦截器链执行顺序与优先级有关,所以正常情况下这个事务会涵盖所有,但是你要是搞AOP的时候设置的优先级高过了事务的优先级,那事务就寄了,同理,你要是在AOP里面把异常给捕获处理了,那最外层感知不到事务自然也寄了

默认的排序如下:

四、失效场景

上面说过了原理就是AOP,底层是动态代理,所以满足以下几点的都会失效:

  1. 调用的不是代理对象
  2. 方法无法被代理
  3. 无异常抛出
  4. 方法存在异步方法(底层采用了TreadLocal,多线程无法传递)

例如:

  1. 类中方法调用内部方法(违反上述第一点)
  2. 方法被final、private等修饰(违反上述第二点)
  3. 手动捕获了异常(违反上述第三点)
  4. 方法中存在异步调用(违反上述第四点)
Last Updated: