月薪10k-20k都无法回答的事务问题,你会吗?

今天朋友扔来一个“简单”的事务代码,初看无味,再看惊奇。

也问了身边的一些朋友无一人回答正确,你也试试?

@Service
public class TestService {

    @Autowired
    private TestMapper testMapper;

    @Autowired
    private TestServiceTwo testServiceTwo;

    @Transactional(rollbackFor = Exception.class)
    public String funOne(){
        // 这个就是一个简单的insert语句
        testMapper.insert("a");
        try {
            testServiceTwo.funTwo();
        }catch (Exception e){
            System.out.println("异常了~~~");
        }
        return "da";
    }
}
@Service
public class TestServiceTwo {

    @Transactional(rollbackFor = Exception.class)
    public void funTwo(){
        throw new RuntimeException("DASDASDA");
    }
}

问:最后数据库插入了几条数据?(答案放在文中)


下面有几个结论我们一个个去证明,看完你就会明白了。


一、spring基于注解的事务是基于代理的,不走代理,被调用的方法就不受事务管理代码的控制

所谓代理其实就是是否是用过依赖注入的,如果我们手动new一个对象,或者直接去调用本类的方法则不走代理。

通过下面这个代码可以打印出当前事务的hashCode,如果hashCode相同则认为是同一个事务。

TransactionAspectSupport.currentTransactionStatus().hashCode()
  • 可以把它放在上面的funOnefunTwo里面,发现最后打印出来的hashCode并不相同。

  • 如果我们调用funTwo的时候,并不使用@Autowired的方式,而是使用new的方式,你会发现打印的hashCode确是相同的。

    • TestServiceTwo two = new TestServiceTwo();
    • two.funTwo();
  • 如果去掉方法funTwo上面的@Transactional注解,再次打印的hashCode也是相同的。

  • 如果不去调用funTwo,而是去调用和funOne同类(同一个类)的funThree,打印的hashCode也是相同的。

注:如果hashCode相同那么说明是用一个事务。


二、嵌套事务如果子事务抛出了异常,父事务同样会回滚,并且会抛出一个异常
Transaction rolled back because it has been marked as rollback-only

其实这个结论就在本题的结果,最后没有插入一条数据,原因就是父事务也回滚了。

funOne调用funTwofunOne中抓了funTwo的异常,当funTwo发生异常的时候,funTwo的操作应该回滚,但是funOne吃了异常,funOne方法中没有产生异常,所以funOne的操作又应该提交,二者是相互矛盾的。

spring的事务关联拦截器在抓到funTwo的异常后就会标记rollback-only为true,当funOne执行完准备提交后,发现rollback-only为true,也会回滚,并抛出异常告诉调用者。


但也不是绝对的,我们知道上面的两个事务虽然是独立的两个事务,但是依旧是有关联的,你可以理解成关联事务(就是上面所说spring的事务关联拦截器…)

如果我们让其事务不进行关联,是两个独立的事务,也就是说不会抛出rollback-only这个异常,那么funOne就不会回滚。

事务的传播行为有很多种,默认是REQUIRED,我们改成REQUIRES_NEW,再来试试结果发现是插入了一条数据的。

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)

事务的传播行为:

valuedesc
REQUIRED (默认)支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择
SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行
MANDATORY支持当前事务,如果当前没有事务,就抛出异常
REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起
NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER以非事务方式执行,如果当前存在事务,则抛出异常
NESTED支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务

注:这里的当前事务和我们一般理解有些不同,不是它的父事务。


三、如果在同一个事务内,不管是父方法异常,还是子方法异常,全部会回滚。(如果把异常try catch,则不会)

这里的同一个事务指(其实我们可以通过打印hashCode的方式来判断)

  • 同一个类去调用子方法
  • 调用另外一个类的没有@Transactional的方法

eg:

@Service
public class TestService {

    @Autowired
    private TestMapper testMapper;

    @Transactional(rollbackFor = Exception.class)
    public String funOne(){
        testMapper.insert("a");
        funThree();
        return "das";
    }

    public void funThree(){
        int i = 1 / 0;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值