月薪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

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

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.classpropagation = Propagation.REQUIRES_NEW)

事务的传播行为:

value desc
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;
    }
}


原文始发于微信公众号(小道仙97):月薪10k-20k都无法回答的事务问题,你会吗?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/41453.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!