相关阅读
Netflix OSS套件一站式学习驿站(Eureka、Hystrix、Ribbon、Feign、Zuul…)
【小家java】java8新特性(简述十大新特性) 饱受赞誉
【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本
【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(一)
【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二)
【小家Spring】源码分析Spring的事务拦截器:TransactionInterceptor和事务管理器:PlatformTransactionManager
每篇一句
技多不压身。知识只有质量,没有重量,压不死人的。
1、概述
想必大家一想到事务,就想到ACID,或者也会想到CAP。但笔者今天不讨论这个,哈哈~本文将从应用层面稍带一点源码,来解释一下我们平时使用事务遇到的一个问题但让很多人又很棘手的问题:Transaction rolled back because it has been marked as rollback-only,中文翻译为:事务已回滚,因为它被标记成了只回滚。囧,中文翻译出来反倒更不好理解了,本文就针对此种事务异常做一个具体分析:
看此篇博文之前,建议先阅读:【小家java】Spring事务不生效的原因大解读
2、栗子
我们如果使用了spring来管理我们的事务,将会使事务的管理变得异常的简单,比如如下方法就有事务:
@Transactional @Override public boolean create(User user) { int i = userMapper.insert(user); System.out.println(1 / 0); //此处抛出异常,事务回滚,因此insert不会生效 return i == 1; }
这应该是我们平时使用的一个缩影。但本文不对事务的基础使用做讨论,只讨论异常情况。但本文可以给读者导航到我的另外一篇博文,介绍了事务不生效的N种可能性:【小家java】spring事务不生效的原因大解读
看下面这个例子,将是我们今天讲述的主题:
@Transactional @Override public boolean create(User user) { int i = userMapper.insert(user); personService.addPerson(user); return i == 1; }
//下面是personService的addPerson方法,也是有事务的
@Transactional @Override public boolean addPerson(User user) { System.out.println(1 / 0); return false; }
这种写法是我们最为普通的写法,显然是可以回滚的。但是如果上面这么写:
@Transactional @Override public boolean create(User user) { int i = userMapper.insert(user); try { personService.addPerson(user); } catch (Exception e) { System.out.println("不断程序,用来输出日志~"); } return i == 1; }
这里我们把别的service方法try住,不希望它阻断我们的程序继续执行。表面上看合乎情理没毛病,but:
这里需要注意:如果我是这么写:
@Transactional @Override public boolean addPerson(User user) { userMapper.updateByIdSelective(user); try { editById(user); } catch (Exception e) { e.printStackTrace(); } return false; } @Transactional @Override public boolean editById(User user) { System.out.println(1 / 0); return false; }
也是不会产生上面所述的那个rollback-only异常的:
@Transactional @Override public boolean addPerson(User user) { try { editById(user); } catch (Exception e) { e.printStackTrace(); } return false; } @Transactional @Override public boolean editById(User user) { userMapper.updateByIdSelective(user); System.out.println(1 / 0); return false; }
但是,我们的updateByIdSelective持久化是生效了的。分析如下:
1.为什么update持久化生效?
因为addPerson有事务,所以editById理论上也有事务应该回滚才对,但是由于上层方法给catch住了,所以是没有回滚的,所以持久化生效。
2.为何没发生roolback-only的异常呢?
原因是因为editById的事务是沿用的addPerson的事务。所以其实上仍然是只有一个事务的,所以catch住不允许回滚也是没有任何问题的,因为事务本身是属于addPerson的,而不属于editById。
但是我们这么来玩:
@Transactional @Override public boolean addPerson(User user) { try { personService.editById(user); } catch (Exception e) { e.printStackTrace(); } return false; } @Transactional @Override public boolean editById(User user) { userMapper.updateByIdSelective(user); System.out.println(1 / 0); return false; }
就毫无疑问会抛出如下异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
但这么玩,去掉addPerson方法的事务,只保留editById的事务呢?
@Override public boolean addPerson(User user) { try { personService.editById(user); } catch (Exception e) { e.printStackTrace(); } return false; } @Transactional @Override public boolean editById(User user) { userMapper.updateByIdSelective(user); System.out.println(1 / 0); return false; }
发现rollback-only异常是永远不会出来的。
因此我们可以得出结论,rollback-only异常,是发生在异常本身才有可能出现,发生在子方法内部是不会出现的。因此这种现象最多是发生在事务嵌套里。