老生常谈 - 引起Spring事务失效的几种情况总结

 2023-01-08
原文作者:Redick01 原文地址:https://juejin.cn/post/7036719557980979213

前言

在使用Spring的事务管理时,有时候就会莫名其妙的发现事务没生效,其实并非Spring事务管理本身的问题,而是开发人员在使用时使用方式不对,Spring事务管理的底层机制没搞懂,又或者数据库层的问题导致,今天总结下Spring事务是失效的集中场景。

常见的Spring事务失效原因

没有被Spring管理

不是由Spring管理的Bean,例如,一个类没有在xml中声明或者没有@Service,@RestController,@Component等注解的方式声明由Spring管理,但在类的方法中使用@Transactional注解声明事务,这种情况事务是不会生效的,也就是说Spring管理的事务只能是在Spring容器管理的Bean中声明才会生效。如下代码,testA方法虽然声明了事务,但是由于@Service注解被注释了,所以事务不会生效。

    //@Service
    public class TransactionService {
    
    
        private final AccountMapper accountMapper;
    
        private Account account = null;
    
        @Autowired
        public TransactionService(AccountMapper accountMapper) {
            this.accountMapper = accountMapper;
        }
    
        @Transactional(propagation = Propagation.NESTED)
        public void testA() {
            insertB();
            account.getBalance();
            insertB();
        }
    
        public void insertA() {
            Account account = new Account();
            account.setBalance(new Long(100));
            account.setCreateTime(new Date());
            account.setFreezeAmount(new Long(0));
            account.setUpdateTime(new Date());
            account.setUserId("123");
            accountMapper.insertSelective(account);
        }
    
        public void insertB() {
            Account account = new Account();
            account.setBalance(new Long(200));
            account.setCreateTime(new Date());
            account.setFreezeAmount(new Long(0));
            account.setUpdateTime(new Date());
            account.setUserId("456");
            accountMapper.insertSelective(account);
        }
    }

发生自调用

先上代码,required方法没有加注解声明事务,required方法调用了testA方法,testA方法声明了事务,但是这里事务不会生效,其原因是因为Spring事务管理是通过代理类实现的,然而这种自调用相当于this.testA(),是通过对象调用,无法走到事务的切面中,所以事务就不生效了。

    @Service
    public class TransactionService {
    
    
        private final AccountMapper accountMapper;
    
        private Account account = null;
    
        @Autowired
        public TransactionService(AccountMapper accountMapper) {
            this.accountMapper = accountMapper;
        }
        
        public void required() {
            insertA();
            testA();
        }
    
        @Transactional
        public void testA() {
            insertB();
            account.getBalance();
            insertB();
        }
    }

再看下面代码,这时required()方法声明了事务,这时testA方法声明了REQUIRES_NEW传播机制的事务,testA方法的事务会生效吗?答案是不会生效,一样的问题产生了自调用,并没有经过Spring的代理,所以事务不生效。

    @Service
    public class TransactionService {
    
    
        private final AccountMapper accountMapper;
    
        private Account account = null;
    
        @Autowired
        public TransactionService(AccountMapper accountMapper) {
            this.accountMapper = accountMapper;
        }
        
        @Transactional
        public void required() {
            insertA();
            testA();
        }
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void testA() {
            insertB();
            account.getBalance();
            insertB();
        }
    }

方法不是public的

来自 Spring 官方文档:

    When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

大概意思就是 @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

数据源未配置事务管理器

    数据源没有配置事务管理器,声明了事务也是白搭,下面是配置事务管理器的代码
        @Bean(name = "transactionManager")
        public DataSourceTransactionManager sentinelTransactionManager(@Qualifier("datasource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }

数据库引擎不支持事务

    以MySQL为例,其MyISAM引擎是不支持事务操作的,InnoDB才是支持事务的引擎,一般要支持事务都会使用InnoDB。如果数据库引擎不支持事务,那也是白搭。

事务传播机制设置以不支持事务运行

    Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起。

异常被catch掉了

    如下面的代码,如果insertB方法中,插入数据库后产生异常,并不会回滚,因为异常被catch调了,事务失效。
        @Transactional
        public void testA() {
            try {
                insertB();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

异常类型错误

    事务默认回滚的异常是RuntimeException,如果想触发其他异常的回滚,需要在注解上配置,例如:
    @Transactional(rollbackFor = Exception.class)

总结

    本文总结了八种事务失效的场景,其实发生最多就是自身调用、异常被吃、异常抛出类型不对这三个了。