简述
在日常开发过程中,我们经常会遇到需要使用事务的场景,尤其是微服务盛行的当下,分布式事务也显得尤为重要,据了解很多使用微服务的项目,并没有处理分布式事务……今天不聊分布式事务,先说说我们在单应用的情况下,会有哪些情况导致事务失效呢?
详解
1. 底层数据库引擎不支持事务
以MySQL为例,它有多种引擎,MyISAM引擎不支持事务操作,InnoDB引擎支持事务,MySQL从5.5.5开始默认的引擎是InnoDB,之前的版本默认的都是MyISAM,所以说如果是非常老的项目,这个需要考虑下。
2. 被@Transactional
注解修饰的方法为非public
类型
如果被@Transactional
注解修饰的方法,修饰符非public
或者被final
修饰,则事务会失效,因为AOP
没办法为这样的方法生成一个代理,自然事务就无法生效。这个在Spring
的官方文档里面也有说明。
在这个地方有个详细讨论过程:https://stackoverflow.com/questions/4396284/does-spring-transactional-attribute-work-on-a-private-method
3. 异常被吃掉了
如果异常被 catch 住,那事务也是会失效呢,伪代码如下:
@Transactional
public void test(){
try{
//插入一条数据
insertA();
//更改一条数据
updateB();
}catch(Exception e){
log.error("异常被捕获了,事务就失效了",e);
}
}
4. 异常抛出类型错误
@Transactional
注解有个属性:rollbackFor
,这个属性可以设置想要回滚的异常,那它默认的异常是什么?
不要犹豫直接告诉我答案,它默认回滚的是:RuntimeException
,如果说我们没有设置这个属性,而且抛出的异常比这个大,那么事务就不会回滚,例如:
@Transactional
public void test(){
try{
//插入一条数据
insertA();
//更改一条数据
updateB();
}catch(Exception e){
// 这个时候事务就不会回滚
throw new Exception("操作失败!");
}
}
5. 本类方法调用
这一个应该是最容易踩坑的了,先来看两段伪代码:
代码一:
@Service
public class TestServiceImpl implements TestService {
public void testA() {
// 查询数据,并进行一些判断
// 调用另外一个方法
testB();
}
@Transactional
public void testB(Test test) {
// update test
}
}
问题:testA()
方法上面没有加 @Transactional
注解,调用有 @Transactional
注解的 testB()
方法,testB()
方法上的事务管用吗?
代码二:
@Service
public class TestServiceImpl implements TestService {
@Transactional
public void testA() {
// 查询数据,并进行一些判断
// 调用另外一个方法
testB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(Test test) {
// update test
}
}
问题:在 testA()
方法上加了 @Transactional
,testB()
的注解上加了 REQUIRES_NEW
新开启一个事务,那么新开的事务管用么?
既然这么问了,答案其实大家都知道了,这两段伪代码的事务都是不管用的。
因为他们是本类方法直接调用,这个时候会用this
关键字,没有经过 Spring 的代理类去调用此方法,从而没有开启事务管理,默认只有在外部调用事务才会生效。
所以说:加事务注解的方法给本类里面的方法调用,事务不生效!
有多种解决方法:
- 最直白的就是把方法拆出来,放在两个类里面(Spring推荐的一种方式);
- 在类里面注入自己,用注入的对象再调用另外一个方法,这个不太优雅;
- 在Spring的配置里面增加一段配置:
<aop:aspectj-autoproxy expose-proxy="true"/>
6. 没有被spring管理
当这个类只是一个普通类,没有被spring
管理成为一个Bean
对象,那它很自然的就不能使用spring
提供的事务管理了,事务自然就不生效。例如:把@Service
注解去掉,这个时候事务就不会生效,小伙伴们可以试试。
总结
以上列举了几种常见的事务失效的原因,其实发生最多就是异常被吃掉了、异常抛出类型错误、本类方法调用不对这三个了,这三点只要在日常码代码的时候注意一下,基本就不会有问题了。
当然事务失效可能还有其他原因,欢迎私信我一起学习,一起成长!
另外,既然说到了Spring的事务,那就不可不提Spring的事务传播机制,总共有7种,一起复习一下,如下:
-
PROPAGATION_REQUIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。
-
PROPAGATION_SUPPORTS:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。
-
PROPAGATION_MANDATORY:该传播级别要求上下文中必须存在事务,否则抛出异常。
-
PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)
-
PROPAGATION_NOT_SUPPORTED:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)
-
PROPAGATION_NEVER:该传播级别要求上下文中不能存在事务,否则抛出异常。
-
PROPAGATION_NESTED:嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务。(save point概念)
有任何问题欢迎微信搜索【Hugh的白板】私信我,一起探讨,一起学习