「Spring」事务失效的场景
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「Spring」事务失效的场景相关的知识,希望对你有一定的参考价值。
参考技术A1.数据库引擎不支持事务
Spring 事务生效的前提是所连接的数据库要支持事务,如果底层的数据库引擎都不支持事务,则Spring的事务肯定会失效。
例如: MySQL 用的不是 InnoDB 引擎,而是用的 MyISAM 存储引擎。
2.事务方法未被 Spring 容器管理
如果事务方法所在的类没有加载到 Spring IOC 容器中,也就是说,事务方法所在的类没有被 Spring 容器管理,则Spring事务会失效。
例如:你的方法所在类没有加@Component或者@Service注解。
3.方法没有被 public 修饰
如果事务所在的方法没有被 public 修饰,此时 Spring 的事务也会失效。
4.同一类中方法之间直接的调用
例如:如果同一个类中有两个方法分别为 A 和 B,方法 A 没有添加事务注解,而方法 B 添加了 @Transactional 事务注解,此时方法 A 直接调用方法 B,则方法 B 的事务会失效。
5.未配置事务管理器
如果在项目中没有配置 Spring 的事务管理器,即使使用了 Spring 的事务管理功能,Spring 的事务也不会生效。
例如:对于 SpringBoot 项目来说,导入了 mybatis 的 starter 依赖后,SpringBoot 会自动注入DataSourceTransactionManager 事务管理器,这样我们就可以直接用 @Transactional 注解使用事务了。
6.事务传播类型不支持事务
如果方法的事务传播类型为不支持事务的传播类型,则该方法的事务在 Spring 中会失效。
例如: A 方法的事务传播类型为 NOT_SUPPORTED,不支持事务,此时用带事务的方法 B 去调用 A 方法,则 A 方法的事务失效。
7.进行异常捕捉却没有抛出
比如对某一个新增数据代码段进行 try catch 异常,而 catch 里没有向外抛出异常,此时 spring 事务无法回滚。
8.错误的标注异常类型
如果在 @Transactional 注解中标注的异常类型不是我们抛出的异常类型,则Spring事务的回滚会失效。
例如: Spring 中默认回滚的异常类型为 RuntimeException,如果此时你抛出的异常是 Exception,那么Spring 事务中无法捕获到 Exception 异常,则事务回滚会失效。
9.开启多线程
开启一个线程去执行数据库操作,多线程内的方法将不被 spring 事务控制。
例如:一个带事务的方法 A 中开启线程去执行同类中的一个 insert 方法,即使这个操作失败了,也不会回滚 A 中的其他数据库操作。
注意:如果把 insert 方法提出到一个新的类中,加入事务注解,就能成功的把 insert 方法加入到 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 领取,更多内容陆续奉上。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
以上是关于「Spring」事务失效的场景的主要内容,如果未能解决你的问题,请参考以下文章