Spring 事务不生效的几种场景

Spring 事务不生效的几种场景

详细内容参考以下链接,这个链接是原文

spring 事务不生效的15中场景

非原创。

以下内容只是为了学习,加深印象,仅作为个人学习笔记, 请支持原创,内容请点击 spring 事务不生效的15中场景

-------------------------------------------------------------------------------分割线-----------------------------------------------------------------------

整理如下几种Spring事务失效的场景

1、service类没有被Spring管理

java 复制代码
//@Service (注释了@Service 或者 忘记添加注解)
public class xxxImpl implements xxxService {

    @Autowired
    private xxMapper xxMapper;
    
     @Autowired
    private yyMapper yyFlowMapper;

    @Transactional
    public void addTianLuo(Domain domain) {
        //do something
        xxMapper.save(domain);
        //do other something
        yyFlowMapper.saveFlow(domain);
    }
}
  • 事务不生效的原因 :上面例子中, @Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。但是@Service被注释后,你的service类都不是spring管理的,那怎么创建代理类来支持事务呢
  • 解决方案 :加上@Service注解。

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

以下案例只针对非Spring Boot 项目

java 复制代码
@Configuration
public class AppConfig {
    // 没有配置事务管理器
}

@Service
public class MyService {
    @Transactional
    public void doSomething() {
        // ...
    }
}
  • 事务不生效的原因 :没有在AppConfig中配置事务管理器,因此Spring无法创建事务代理对象,导致事务不生效。即使在MyService中添加了@Transactional注解,该方法也不会被Spring管理的事务代理拦截。
  • 解决方案 :为了解决这个问题,应该在AppConfig中配置一个事务管器。例如:
java 复制代码
@Configuration
public class AppConfig {
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

@Service
public class MyService {
    @Transactional
    public void doSomething() {
        // ...
    }
}

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

java 复制代码
@Service
public class xxImpl implements xxxService {

    @Autowired
    private xxMapper xxMapper;
     

    @Transactional
    public final void addTianLuo(Domain domain) {
         
        xxMapper.save(domain); 
        
        xxMapper.saveOther(domain);
    }
}
  • 事务不生效的原因 :如果一个方法被声明为final或者static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。
  • 解决方案addTianLuo事务方法 不要用final修饰或者static修饰。

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

java 复制代码
@Service
public class xxServiceImpl implements xxService {

    @Autowired
    private xxMapper xxMapper;
   
    public void addTianLuo(Domain domain){
     // 调用内部的事务方法
     this.executeAddMethod(domain);
   }

    @Transactional
    public void executeAddMethod(Domain domain) {
         xxMapper.save(domain); 
        
        xxMapper.saveOther(domain);
    }
}
  • 事务不生效的原因 : 事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用 。即以上代码,调用目标executeAddTianLuo方法不是通过代理类进行的,因此事务不生效。
  • 解决方案:可以新建多一个类,让这两个方法分开,分别在不同的类中, 当然也可以注入自己,然后调用,这种方法不太优雅。

总结:

以上几种类型都是使用了声明式的@Transaction事务注解,从spring Aop 为事务类生成 代理对象的角度出发,当类没有被IOC管理、使用了不当的修饰符、没有使用spring 生产的代理对象的方法(内部方法直接调用)等导致事务无法生效。

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

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

7、配置错误的 @Transactional 注解

java 复制代码
@Transactional(readOnly = true)
public void updateUser(User user) {
    userDao.updateUser(user);
}
  • 事务不生效的原因 :虽然使用了@Transactional注解,但是注解中的readOnly=true属性指示这是一个只读事务,因此在更新User实体时会抛出异常。
  • 解决方案 :将readOnly属性设置为false,或者移除了@Transactional注解中的readOnly属性。

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

java 复制代码
@Transactional(timeout = 1)
public void doSomething() {
    //...
}

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

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

java 复制代码
@Service
public class xxServiceImpl {
 
    @Autowired
    private XXMapper xxMapper;
    @Autowired
    private XXFlowMapper xxFlowMapper;
 
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public  void doInsertTianluo(XX xx) throws Exception {
        xxMapper.save(xx);
        xxFlowMapper.saveFlow(xx);
    }
}
  • 事务不生效的原因Propagation.NOT_SUPPORTED传播特性不支持事务。
  • 解决方案:选择正确的事务传播机制。

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

  • REQUIRED(默认):如果当前存在一个事务,则加入该事务;否则,创建一个新事务。该传播级别表示方法必须在事务中执行。
  • SUPPORTS:如果当前存在一个事务,则加入该事务;否则,以非事务的方式继续执行。
  • MANDATORY:如果当前存在一个事务,则加入该事务;否则,抛出异常。
  • REQUIRES_NEW:创建一个新的事务,并且如果存在一个事务,则将该事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。
  • NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
  • NESTED:如果当前存在一个事务,则在嵌套事务内执行。如果没有事务,则按REQUIRED传播级别执行。嵌套事务是外部事务的一部分,可以在外部事务提交或回滚时部分提交或回滚。

10. rollbackFor属性配置错误

java 复制代码
@Service
public class XXerviceImpl implements XXService {

    @Autowired
    private XXMapper xxMapper;
    
    @Autowired
    private XXFlowMapper xxFlowMapper;

    @Transactional(rollbackFor = Error.class)
    public void addTianLuo(XX xx) {
        //
        xxMapper.save(xx);
        //
        xxFlowMapper.saveFlow(xx);
        //模拟异常抛出
        throw new Exception();
    }
}
  • 事务不生效的原因 : 其实rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeExceptionError两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效。

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

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

java 复制代码
public class MyService {

    @Autowired
    private MyRepository myRepository;

    @Transactional
    public void doSomething(String data) {
        myRepository.save(data);
    }
}

public class MyTianluoService extends MyService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void doSomething(String data) {
        super.doSomething(data);
    }
}
  • 事务失效的原因MyTianluoServiceMyService的子类,并且覆盖了doSomething()方法。在该方法中,使用了不同的传播行为(REQUIRES_NEW)来覆盖父类的@Transactional注解。在这种情况下,当调用MyTianluoServicedoSomething()方法时,由于子类方法中的注解覆盖了父类的注解,Spring框架将不会在父类的方法中启动事务 。因此,当MyRepositorysave()方法被调用时,事务将不会被启动,也不会回滚。这将导致数据不一致的问题,因为在MyRepositorysave()方法中进行的数据库操作将不会回滚。

12.嵌套事务的坑

java 复制代码
@Service
public class TianLuoServiceInOutService {

    @Autowired
    private TianLuoFlowService tianLuoFlowService;
    @Autowired
    private TianLuoMapper tianLuoMapper;

    @Transactional
    public void addTianLuo(TianLuo tianluo) throws Exception {
        tianLuoMapper.save(tianluo);// 因tianLuoFlowService.saveFlow事务回滚,该方法事务也回滚
        tianLuoFlowService.saveFlow(tianluo);
    }
}

@Service
public class TianLuoFlowService {

    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;

    @Transactional(propagation = Propagation.NESTED)
    public void saveFlow(TianLuo tianLuo) {
        tianLuoFlowMapper.save(tianLuo);
        throw new RuntimeException(); //继续往上抛
    }
}

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

java 复制代码
    @Transactional
    public void addTianLuo(TianLuo tianluo) throws Exception {
        tianLuoMapper.save(tianluo);
        try {
            tianLuoFlowService.saveFlow(tianluo);
        } catch (Exception e) {
          log.error("save tian luo flow fail,message:{}",e.getMessage());
        }
    }

13. 事务多线程调用

java 复制代码
@Service
public class TianLuoService {

    @Autowired
    private TianLuoMapper tianLuoMapper;

    @Autowired
    private TianLuoFlowService tianLuoFlowService;

    @Transactional
    public void addTianLuo(TianLuo tianluo) {
        //保存tianluo数据库记录
        tianLuoMapper.save(tianluo);
        //多线程调用
        new Thread(() -> {
            tianLuoFlowService.saveFlow(tianluo);
        }).start();
    }
}

@Service
public class TianLuoFlowService {

    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;

    @Transactional
    public void save(TianLuo tianLuo) {
        tianLuoFlowMapper.saveFlow(tianLuo);
    }
}

事务不生效原因 :这是因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。

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

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

java 复制代码
@Service
public class TianLuoServiceImpl implements TianLuoService {
    @Transactional
    public void addTianLuo(TianLuo tianluo) {
        try {
           ...
        } catch (Exception e) {
            log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
        }
    }
}
  • 事务不生效的原因 : 事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚。我们可以从spring源码(TransactionAspectSupport这个类)中找到答案:

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

15.手动抛了别的异常

以上面的第10点比较类似

java 复制代码
@Service
public class TianLuoServiceImpl implements TianLuoService {

    @Autowired
    private TianLuoMapper tianLuoMapper;
    
    @Autowired
    private TianLuoFlowMapper tianLuoFlowMapper;

    @Transactional
    public void addTianLuo(TianLuo tianluo) throws Exception {
        //保存tianluo数据库记录
        tianLuoMapper.save(tianluo);
        //保存tianluo流水数据库记录
        tianLuoFlowMapper.saveFlow(tianluo);
        throw new Exception();
    }
}

与上面的第10点比较类似

  • 失效的原因 :上面的代码例子中,手动抛了Exception异常,但是是不会回滚的,因为Spring默认只处理RuntimeException和Error,对于普通的Exception不会回滚,除非,用rollbackFor属性指定配置。
  • 解决方案 :添加属性配置@Transactional(rollbackFor = Exception.class)

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

非原创,详细内容参考以下链接,这个链接是原文:

spring 事务不生效的15中场景

此内容只是为了学习,加深印象,仅作为个人学习笔记, 请支持原创, 请支持原创,内容请点击 spring 事务不生效的15中场景

相关推荐
weixin_462428472 分钟前
使用 Caffeine 缓存并在业务方法上通过注解实现每3到5秒更新缓存
java·缓存
程序媛小果4 分钟前
基于java+SpringBoot+Vue的桂林旅游景点导游平台设计与实现
java·vue.js·spring boot
骑鱼过海的猫1236 分钟前
【java】java通过s3访问ceph报错
java·ceph·iphone
杨充12 分钟前
13.观察者模式设计思想
java·redis·观察者模式
Lizhihao_14 分钟前
JAVA-队列
java·开发语言
喵叔哟23 分钟前
重构代码之移动字段
java·数据库·重构
喵叔哟23 分钟前
重构代码之取消临时字段
java·前端·重构
fa_lsyk26 分钟前
maven环境搭建
java·maven
Daniel 大东1 小时前
idea 解决缓存损坏问题
java·缓存·intellij-idea
wind瑞1 小时前
IntelliJ IDEA插件开发-代码补全插件入门开发
java·ide·intellij-idea