这12种场景Spring事务会失效!
Posted 雨点的名字
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这12种场景Spring事务会失效!相关的知识,希望对你有一定的参考价值。
聊聊spring事务失效的12种场景,太坑了
SUPPORTS
如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。MANDATORY
如果当前上下文中存在事务,否则抛出异常。REQUIRES_NEW
每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。NOT_SUPPORTED
如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。NEVER
如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。NESTED
如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。如果我们在手动设置propagation参数的时候,把传播特性设置错了,比如:
@Service
public class UserService
@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel)
saveData(userModel);
updateData(userModel);
我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。
目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。
2.自己吞了异常事务不会回滚,最常见的问题是:开发者在代码中手动try...catch了异常。比如:
@Slf4j
@Service
public class UserService
@Transactional
public void add(UserModel userModel)
try
saveData(userModel);
updateData(userModel);
catch (Exception e)
log.error(e.getMessage(), e);
这种情况下spring事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。
如果想要spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。
3.手动抛了别的异常即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。
@Slf4j
@Service
public class UserService
@Transactional
public void add(UserModel userModel) throws Exception
try
saveData(userModel);
updateData(userModel);
catch (Exception e)
log.error(e.getMessage(), e);
throw new Exception(e);
上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。
因为spring事务,默认情况下只会回滚RuntimeException
(运行时异常)和Error
(错误),对于普通的Exception(非运行时异常),它不会回滚。
在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor
参数,来完成这个功能。
但如果这个参数的值设置错了,就会引出一些莫名其妙的问题,例如:
@Slf4j
@Service
public class UserService
@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception
saveData(userModel);
updateData(userModel);
如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。
即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。
这是为什么呢?
因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成:Exception或Throwable。
5.嵌套事务回滚多了public class UserService
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception
userMapper.insertUser(userModel);
roleService.doOtherThing();
@Service
public class RoleService
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing()
System.out.println("保存role表数据");
这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。。但事实是,insertUser也回滚了。
why?
因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
怎么样才能只回滚保存点呢?
@Slf4j
@Service
public class UserService
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception
userMapper.insertUser(userModel);
try
roleService.doOtherThing();
catch (Exception e)
log.error(e.getMessage(), e);
可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。
三 其他1 大事务问题在使用spring事务时,有个让人非常头疼的问题,就是大事务问题。
通常情况下,我们会在方法上@Transactional
注解,填加事务功能,比如:
@Service
public class UserService
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception
query1();
query2();
query3();
roleService.save(userModel);
update(userModel);
@Service
public class RoleService
@Autowired
private RoleService roleService;
@Transactional
public void save(UserModel userModel) throws Exception
query4();
query5();
query6();
saveData(userModel);
但@Transactional
注解,如果被加到方法上,有个缺点就是整个方法都包含在事务当中了。
上面的这个例子中,在UserService类中,其实只有这两行才需要事务:
roleService.save(userModel);
update(userModel);
在RoleService类中,只有这一行需要事务:
saveData(userModel);
现在的这种写法,会导致所有的query方法也被包含在同一个事务当中。
如果query方法非常多,调用层级很深,而且有部分查询方法比较耗时的话,会造成整个事务非常耗时,而从造成大事务问题。
2.编程式事务上面聊的这些内容都是基于@Transactional
注解的,主要说的是它的事务问题,我们把这种事务叫做:声明式事务
。
其实,spring还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:编程式事务
。例如:
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user)
queryData1();
queryData2();
transactionTemplate.execute((status) =>
addData1();
updateData2();
return Boolean.TRUE;
)
在spring中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的execute方法中,就实现了事务的功能。
相较于@Transactional
注解声明式事务,我更建议大家使用,基于TransactionTemplate
的编程式事务。主要原因如下:
避免由于spring aop问题,导致事务失效的问题。 能够更小粒度的控制事务的范围,更直观。
建议在项目中少使用@Transactional注解开启事务。但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用@Transactional注解开启事务开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。
1. 用IDEA调试JDK源码 (含阅读调试源码的技巧)
2. Redis 过滤请求绝技 — 布隆过滤器与布谷鸟过滤器
3. 阿里二面:main 方法可以继承吗?
4. SpringBoot + SpringBatch + Quartz整合定时批量任务
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
以上是关于这12种场景Spring事务会失效!的主要内容,如果未能解决你的问题,请参考以下文章