0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Spring事务在哪几种情况下会不生效?

jf_ro2CN3Fa 来源:捡田螺的小男孩 2023-05-10 17:53 次阅读

前言

日常开发中,我们经常使用到spring事务。最近一位朋友去美团面试,被问了这么一道面试题: Spring 事务在哪几种情况下会不生效? 今天田螺哥跟大家聊聊,spring事务不生效 的15种场景。

1. 你的service类没有被Spring管理

//@Service(注释了@Service)
publicclassTianLuoServiceImplimplementsTianLuoService{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional
publicvoidaddTianLuo(TianLuotianluo){
//保存tianluo实体数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo流水数据库记录
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

事务不生效的原因 :上面例子中, @Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。但是@Service被注释后,你的service类都不是spring管理的,那怎么创建代理类来支持事务呢

解决方案 :加上@Service注解。

2.没有在Spring配置文件中启用事务管理器

@Configuration
publicclassAppConfig{
//没有配置事务管理器
}

@Service
publicclassMyService{
@Transactional
publicvoiddoSomething(){
//...
}
}

事务不生效的原因 :没有在AppConfig中配置事务管理器,因此Spring无法创建事务代理对象,导致事务不生效。即使在MyService中添加了@Transactional注解,该方法也不会被Spring管理的事务代理拦截。

解决方案 :为了解决这个问题,应该在AppConfig中配置一个事务管器。例如:

@Configuration
publicclassAppConfig{
@Bean
publicPlatformTransactionManagertransactionManager(){
returnnewDataSourceTransactionManager(dataSource());
}
}

@Service
publicclassMyService{
@Transactional
publicvoiddoSomething(){
//...
}
}

如果是Spring Boot项目,它默认会自动配置事务管理器并开启事务支持。

3. 事务方法被final、static关键字修饰

@Service
publicclassTianLuoServiceImpl{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional
publicfinalvoidaddTianLuo(TianLuotianluo){
//保存tianluo实体数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo流水数据库记录
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

事务不生效的原因 :如果一个方法被声明为final或者static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。

解决方案 :addTianLuo事务方法 不要用final修饰或者static修饰。

4. 同一个类中,方法内部调用

@Service
publicclassTianLuoServiceImplimplementsTianLuoService{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

publicvoidaddTianLuo(TianLuotianluo){
//调用内部的事务方法
this.executeAddTianLuo(tianluo);
}

@Transactional
publicvoidexecuteAddTianLuo(TianLuotianluo){
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

事务不生效的原因 : 事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用 。即以上代码,调用目标executeAddTianLuo方法不是通过代理类进行的,因此事务不生效。

解决方案 :可以新建多一个类,让这两个方法分开,分别在不同的类中。如下:

@Service
publicclassTianLuoExecuteServiceImplimplementsTianLuoExecuteService{

@Autowired
privateTianLuoMappertianLuoMapper;
@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional
publicvoidexecuteAddTianLuo(TianLuotianluo){
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

@Service
publicclassTianLuoAddServiceImplimplementsTianLuoAddService{

@Autowired
privateTianLuoExecuteServicetianLuoExecuteService;

publicvoidaddTianLuo(Useruser){
tianLuoExecuteService.executeAddTianLuo(user);
}
}

当然,有时候你也可以在该 Service 类中注入自己,或者通过AopContext.currentProxy()获取代理对象。

5.方法的访问权限不是public

@Service
publicclassTianLuoServiceImplimplementsTianLuoService{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional
privatevoidaddTianLuo(TianLuotianluo){
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

事务不生效的原因 :spring事务方法addTianLuo的访问权限不是public,所以事务就不生效啦,因为Spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,也就是这时事务属性不存在了。大家可以看下AbstractFallbackTransactionAttributeSource的源码:

86e87cbe-e41d-11ed-ab56-dac502259ad0.png

解决方案 :addTianLuo事务方法的访问权限修改为public。

6. 数据库的存储引擎不支持事务

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,确认你的选择的存储引擎是支持事务的

870c83f2-e41d-11ed-ab56-dac502259ad0.png

7 .配置错误的 @Transactional 注解

@Transactional(readOnly=true)
publicvoidupdateUser(Useruser){
userDao.updateUser(user);
}

事务不生效的原因 :虽然使用了@Transactional注解,但是注解中的readOnly=true属性指示这是一个只读事务,因此在更新User实体时会抛出异常。

解决方案 :将readOnly属性设置为false,或者移除了@Transactional注解中的readOnly属性。

8.事务超时时间设置过短

@Transactional(timeout=1)
publicvoiddoSomething(){
//...
}

事务不生效的原因 :在上面的例子中,timeout属性被设置为1秒,这意味着如果事务在1 秒内无法完成,则报事务超时了。

9. 使用了错误的事务传播机制

@Service
publicclassTianLuoServiceImpl{

@Autowired
privateTianLuoMappertianLuoMapper;
@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional(propagation=Propagation.NOT_SUPPORTED)
publicvoiddoInsertTianluo(TianLuotianluo)throwsException{
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

事务不生效的原因 :Propagation.NOT_SUPPORTED传播特性不支持事务。

解决方案 :选择正确的事务传播机制。

帮大家复习一下,Spring提供了七种事务传播机制。它们分别是:

REQUIRED(默认):如果当前存在一个事务,则加入该事务;否则,创建一个新事务。该传播级别表示方法必须在事务中执行。

SUPPORTS:如果当前存在一个事务,则加入该事务;否则,以非事务的方式继续执行。

MANDATORY:如果当前存在一个事务,则加入该事务;否则,抛出异常。

REQUIRES_NEW:创建一个新的事务,并且如果存在一个事务,则将该事务挂起。

NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。

NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。

NESTED:如果当前存在一个事务,则在嵌套事务内执行。如果没有事务,则按REQUIRED传播级别执行。嵌套事务是外部事务的一部分,可以在外部事务提交或回滚时部分提交或回滚。

10. rollbackFor属性配置错误

@Service
publicclassTianLuoServiceImplimplementsTianLuoService{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional(rollbackFor=Error.class)
publicvoidaddTianLuo(TianLuotianluo){
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo流水数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
//模拟异常抛出
thrownewException();
}
}

事务不生效的原因 : 其实rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeException和Error两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效。

871cf43a-e41d-11ed-ab56-dac502259ad0.png

大家可以看一下Transactional注解源码哈:

872f0f30-e41d-11ed-ab56-dac502259ad0.png

解决方案 :rollbackFor属性指定的异常与抛出的异常匹配。

11.事务注解被覆盖导致事务失效

publicinterfaceMyRepository{
@Transactional
voidsave(Stringdata);
}

publicclassMyRepositoryImplimplementsMyRepository{
@Override
publicvoidsave(Stringdata){
//数据库操作
}
}

publicclassMyService{

@Autowired
privateMyRepositorymyRepository;

@Transactional
publicvoiddoSomething(Stringdata){
myRepository.save(data);
}
}

publicclassMyTianluoServiceextendsMyService{
@Transactional(propagation=Propagation.REQUIRES_NEW)
publicvoiddoSomething(Stringdata){
super.doSomething(data);
}
}

事务失效的原因 :MyTianluoService是MyService的子类,并且覆盖了doSomething()方法。在该方法中,使用了不同的传播行为(REQUIRES_NEW)来覆盖父类的@Transactional注解。在这种情况下,当调用MyTianluoService的doSomething()方法时,由于子类方法中的注解覆盖了父类的注解,Spring框架将不会在父类的方法中启动事务 。因此,当MyRepository的save()方法被调用时,事务将不会被启动,也不会回滚。这将导致数据不一致的问题,因为在MyRepository的save()方法中进行的数据库操作将不会回滚。

12.嵌套事务的坑

@Service
publicclassTianLuoServiceInOutService{

@Autowired
privateTianLuoFlowServicetianLuoFlowService;
@Autowired
privateTianLuoMappertianLuoMapper;

@Transactional
publicvoidaddTianLuo(TianLuotianluo)throwsException{
tianLuoMapper.save(tianluo);
tianLuoFlowService.saveFlow(tianluo);
}
}

@Service
publicclassTianLuoFlowService{

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional(propagation=Propagation.NESTED)
publicvoidsaveFlow(TianLuotianLuo){
tianLuoFlowMapper.save(tianLuo);
thrownewRuntimeException();
}
}

以上代码使用了嵌套事务,如果saveFlow出现运行时异常,会继续往上抛,到外层addTianLuo的方法,导致tianLuoMapper.save也会回滚啦。如果不想因为被内部嵌套的事务影响 ,可以用try-catch包住,如下:

@Transactional
publicvoidaddTianLuo(TianLuotianluo)throwsException{
tianLuoMapper.save(tianluo);
try{
tianLuoFlowService.saveFlow(tianluo);
}catch(Exceptione){
log.error("savetianluoflowfail,message:{}",e.getMessage());
}
}

13. 事务多线程调用

@Service
publicclassTianLuoService{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowServicetianLuoFlowService;

@Transactional
publicvoidaddTianLuo(TianLuotianluo){
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//多线程调用
newThread(()->{
tianLuoFlowService.saveFlow(tianluo);
}).start();
}
}

@Service
publicclassTianLuoFlowService{

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional
publicvoidsave(TianLuotianLuo){
tianLuoFlowMapper.saveFlow(tianLuo);
}
}

事务不生效原因 :这是因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文 ,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。Spring事务管理器通过使用线程本地变量(ThreadLocal)来实现线程安全。大家有兴趣的话,可以去看下源码哈.

在Spring事务管理器中,通过TransactionSynchronizationManager类来管理事务上下文。TransactionSynchronizationManager内部维护了一个ThreadLocal对象,用来存储当前线程的事务上下文。在事务开始时,TransactionSynchronizationManager会将事务上下文绑定到当前线程的ThreadLocal对象中,当事务结束时,TransactionSynchronizationManager会将事务上下文从ThreadLocal对象中移除。

8779cb2e-e41d-11ed-ab56-dac502259ad0.png

14.异常被捕获并处理了,没有重新抛出

@Service
publicclassTianLuoServiceImplimplementsTianLuoService{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional
publicvoidaddTianLuo(TianLuotianluo){
try{
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluoflow数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
}catch(Exceptione){
log.error("addTianLuoerror,id:{},message:{}",tianluo.getId(),e.getMessage());
}
}

}

事务不生效的原因 : 事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚。我们可以从spring源码(TransactionAspectSupport这个类)中找到答案:

publicabstractclassTransactionAspectSupportimplementsBeanFactoryAware,InitializingBean{

//这方法会省略部分代码,只留关键代码哈
@Nullable
protectedObjectinvokeWithinTransaction(Methodmethod,@NullableClasstargetClass,finalInvocationCallbackinvocation)throwsThrowable{

if(txAttr==null||!(ptminstanceofCallbackPreferringPlatformTransactionManager)){

TransactionInfotxInfo=createTransactionIfNecessary(ptm,txAttr,joinpointIdentification);
ObjectretVal;
try{
//SpringAOP中MethodInterceptor接口的一个方法,它允许拦截器在执行被代理方法之前和之后执行额外的逻辑。
retVal=invocation.proceedWithInvocation();
}
catch(Throwableex){
//用于在发生异常时完成事务(如果Springcatch不到对应的异常的话,就不会进入回滚事务的逻辑)
completeTransactionAfterThrowing(txInfo,ex);
throwex;
}
finally{
cleanupTransactionInfo(txInfo);
}

//用于在方法正常返回后提交事务。
commitTransactionAfterReturning(txInfo);
returnretVal;
}
}

在invokeWithinTransaction方法中,当Spring catch到Throwable异常的时候,就会调用completeTransactionAfterThrowing()方法进行事务回滚的逻辑。但是,在TianLuoServiceImpl类的spring事务方法addTianLuo中,直接把异常catch住了,并没有重新throw出来,因此 Spring自然就catch不到异常啦,因此事务回滚的逻辑就不会执行,事务就失效了。

解决方案 :在spring事务方法中,当我们使用了try-catch,如果catch住异常,记录完异常日志什么的,一定要重新把异常抛出来,正例如下:

@Service
publicclassTianLuoServiceImplimplementsTianLuoService{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional(rollbackFor=Exception.class)
publicvoidaddTianLuo(TianLuotianluo){
try{
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluoflow数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
}catch(Exceptione){
log.error("addTianLuoerror,id:{},message:{}",tianluo.getId(),e.getMessage());
throwe;
}
}
}

15. 手动抛了别的异常

@Service
publicclassTianLuoServiceImplimplementsTianLuoService{

@Autowired
privateTianLuoMappertianLuoMapper;

@Autowired
privateTianLuoFlowMappertianLuoFlowMapper;

@Transactional
publicvoidaddTianLuo(TianLuotianluo)throwsException{
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo流水数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
thrownewException();
}
}

失效的原因 :上面的代码例子中,手动抛了Exception异常,但是是不会回滚的,因为Spring默认只处理RuntimeException和Error,对于普通的Exception不会回滚,除非,用rollbackFor属性指定配置。

解决方案:添加属性配置@Transactional(rollbackFor = Exception.class)。

注解为事务范围的方法中,事务的回滚仅仅对于unchecked的异常有效。对于checked异常无效。也就是说事务回滚仅仅发生在,出现RuntimeException或Error的时候。通俗一点就是:代码中出现的空指针等异常,会被回滚。而文件读写、网络超时问题等,spring就没法回滚了。




审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • MySQL
    +关注

    关注

    1

    文章

    775

    浏览量

    26005
  • AOP
    AOP
    +关注

    关注

    0

    文章

    37

    浏览量

    11043
  • MYSQL数据库
    +关注

    关注

    0

    文章

    95

    浏览量

    9277

原文标题:美团二面:spring事务不生效的15种场景

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    EMI滤波器有哪几种应用和选择?

    EMI滤波器有哪几种应用和选择?|深圳比创达EMC
    的头像 发表于 10-09 10:32 884次阅读
    EMI滤波器有<b class='flag-5'>哪几种</b>应用和选择?

    STM32 IO口的工作模式有哪几种

    STM32 IO口的工作模式有哪几种?这几种模式在什么情况下去使用啊?
    发表于 07-28 08:21

    JLink下载不了的情况哪几种

    JLink为什么会下载不了呢?JLink下载不了的情况哪几种
    发表于 10-21 09:25

    直流屏充电模块什么样的情况会被烧坏呢?有哪几种情况

    直流屏充电模块什么样的情况下会被烧坏呢?有哪几种情况?麻烦大家给分析分析,谢谢
    发表于 07-24 10:54

    ABS的配置有哪几种?

    ABS的配置有哪几种:ABS的种类有四种:1-2条控制回路的后轮ABS;2条控制回路的4轮交叉式ABS;3条控制回路的4轮ABS;4条控制回路的4轮ABS; ABS是Antilock Braking System的简称,它的
    发表于 05-24 00:13 2264次阅读

    电视制式主有要哪几种

    电视制式主有要哪几种              目前,
    发表于 12-29 17:59 2387次阅读

    料位开关有哪几种

    本视频主要详细介绍了料位开关有哪几种,分别是阻旋料位开关、射频导纳料位开关、振动式料位开关等。
    的头像 发表于 01-10 15:49 1.1w次阅读

    在哪几种情况下会造成伺服电机抖动,怎样解决

    在哪几种情况下会造成伺服电机抖动?怎样才能解决这些伺服电机抖动带来的问题?分别是怎么解决的? 例如:加减速时间设置得过小,伺服电机在突然的启动或者停止的时候会产生高惯性抖动......分别把加减
    的头像 发表于 11-05 10:40 8096次阅读

    在哪几种情况下会造成伺服电机抖动

    在哪几种情况下会造成伺服电机抖动?怎样才能解决这些伺服电机抖动带来的问题?分别是怎么解决的?
    的头像 发表于 02-22 16:14 1708次阅读

    风机轴维修有哪几种工艺

    风机轴维修有哪几种工艺
    发表于 12-03 17:47 9次下载

    轴承跑内圆有哪几种修复方式

    轴承跑内圆有哪几种修复方式
    发表于 01-23 11:07 7次下载

    轴承孔磨损维修有哪几种方法?

    轴承孔磨损维修有哪几种方法?
    发表于 04-01 16:30 11次下载

    ESD模型有哪几种你知道吗?

    ESD模型有哪几种你知道吗?
    的头像 发表于 05-09 10:00 1179次阅读
    ESD模型有<b class='flag-5'>哪几种</b>你知道吗?

    在哪几种情况下会造成伺服电机抖动?

    在哪几种情况下会造成伺服电机抖动?怎样才能解决这些伺服电机抖动带来的问题?分别是怎么解决的?
    发表于 05-24 09:41 199次阅读

    什么是步进电机?步进电机分哪几种?

    电子发烧友网站提供《什么是步进电机?步进电机分哪几种?.pdf》资料免费下载
    发表于 11-28 14:21 1次下载
    什么是步进电机?步进电机分<b class='flag-5'>哪几种</b>?