spring同类调用事务不生效-原因及三种解决方式

spring提供的声明式事务注解@Transactional,极大的方便了开发者管理事务,无需手动编写开启、提交、回滚事务的代码。
但是也带来了一些隐患,如果注解使用不当,可能导致事务不生效,最终导致脏数据也入库。

如果在同一个类直接调用事务方法,就会导致事务不生效,示例如下

 

public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Override
    public void insertStudent(){
        insert();
    }

    @Transactional(rollbackFor = Exception.class)
    public void insert() {
        StudentDO studentDO = new StudentDO();
        studentDO.setName("小民");
        studentDO.setAge(22);
        studentMapper.insert(studentDO);

        if (studentDO.getAge() > 18) {
            throw new RuntimeException("年龄不能大于18岁");
        }
    }
}

 

事务不生效的原因在于,spring基于AOP机制实现事务的管理,@Authwired StudentService studentService这样的方式,调用StudentService的方法时,实际上是通过StudentService的代理类调用StudentService的方法,代理类在执行目标方法前后,加上了事务管理的代码。

image.png

 

因此,只有通过注入的StudentService调用事务方法,才会走代理类,才会执行事务管理;如果在同类直接调用,没走代理类,事务就无效。
注意:除了@Transactional,@Async同样需要代理类调用,异步才会生效

 

但是在实际的业务场景中,同类调用事务方法难以避免,怎么让同类调用时事务依然生效呢?有以下三个方法

方法一

自己@Autowired自己,示例如下

 

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Autowired
    private StudentService studentService;

    @Override
    public void insertStudent(){
        studentService.insert();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insert() {
        StudentDO studentDO = new StudentDO();
        studentDO.setName("小民");
        studentDO.setAge(22);
        studentMapper.insert(studentDO);

        if (studentDO.getAge() > 18) {
            throw new RuntimeException("年龄不能大于18岁");
        }
    }
}

可能有人会担心这样会有循环依赖的问题,事实上,spring通过三级缓存解决了循环依赖的问题,所以上面的写法不会有循环依赖问题。
但是!!!,这不代表spring永远没有循环依赖的问题(@Async导致循环依赖了解下)

 

方法二

使用AopContext获取到当前代理类,需要在启动类加上@EnableAspectJAutoProxy(exposeProxy = true),
示例如下

 

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Override
    public void insertStudent(){
        getService().insert();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insert() {
        StudentDO studentDO = new StudentDO();
        studentDO.setName("小民");
        studentDO.setAge(22);
        studentMapper.insert(studentDO);

        if (studentDO.getAge() > 18) {
            throw new RuntimeException("年龄不能大于18岁");
        }
    }

    /**
     * 通过AopContext获取代理类
     * @return StudentService代理类
     */
    private StudentService getService(){
        return Objects.nonNull(AopContext.currentProxy()) ? (StudentService)AopContext.currentProxy() : this;
    }
}

exposeProxy = true用于控制AOP框架公开代理,公开后才可以通过AopContext获取到当前代理类。(默认情况下不会公开代理,因为会降低性能)
注意:不能保证这种方式一定有效,使用@Async时,本方式可能失效。
从@Async案例找到Spring框架的bug:exposeProxy=true不生效原因大剖析

image.png

 

 

方式三(推荐)

通过spring上下文获取到当前代理类,示例如下

 

@Component
public class SpringBeanUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 通过class获取Bean
     * @param clazz class
     * @param <T> 泛型
     * @return bean
     */
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

 

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Override
    public void insertStudent(){
        StudentService bean = SpringBeanUtil.getBean(StudentService.class);
        if (null != bean) {
            bean.insert();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insert() {
        StudentDO studentDO = new StudentDO();
        studentDO.setName("小民");
        studentDO.setAge(22);
        studentMapper.insert(studentDO);

        if (studentDO.getAge() > 18) {
            throw new RuntimeException("年龄不能大于18岁");
        }
    }
}

当一个类实现ApplicationContextAware接口后,就可以方便的获得ApplicationContext中的所有bean。



作者:修行者12138
链接:https://www.jianshu.com/p/083605986c8f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

引用\[1\]:Spring事务的原理是通过@Transactional注解来配置事务的传播级别和异常处理方式。其中,@Transactional(propagation = Propagation.NOT_SUPPORTED)表示不支持事务,若存在事务则挂起;@Transactional(propagation = Propagation.NEVER)表示不使用事务,若存在事务则抛出异常。解决方案是使用Spring默认的传播级别(PROPAGATION_REQUIRED),或其他支持事务的传播级别。\[1\] 引用\[2\]:Spring事务默认支持RuntimeException异常,抛出的异常为RuntimeException异常及其子类异常事务均可生效。但若手动抛出Exception异常,而Exception是RuntimeException的父类,会导致事务不生效解决方案有两种:一是指定Spring事务异常捕获类型@Transactional(rollbackFor = Exception.class),二是抛出Spring事务支持的异常类型throw new RuntimeException("手动抛出运行时异常")。\[2\] 引用\[3\]:Spring事务在后端开发中是非常常见的,它是保证数据一致性的基本保证。在Spring中,我们可以通过对方法进行事务的配置来实现事务操作,而不需要手动编写事务代码,这大大降低了开发的难度。然而,有时候我们会遇到事务不生效的问题。这篇文章针对这个问题进行了具体的分析和举例,总结了15种场景下Spring事务不生效原因解决方案。\[3\] 问题: spring事务不生效的15种场景有哪些? 回答: Spring事务不生效的15种场景包括但不限于: 1. 使用了@Transactional注解但未开启事务; 2. 使用了try-catch块捕获了异常; 3. 方法没有被Spring容器管理; 4. 事务方法被同一个类中的其他非事务方法调用; 5. 事务方法被同一个类中的其他事务方法调用,但没有使用代理对象调用; 6. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务传播级别; 7. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务隔离级别; 8. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务超时时间; 9. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务只读属性; 10. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务回滚规则; 11. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务传播行为; 12. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务传播行为和事务隔离级别; 13. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务传播行为和事务超时时间; 14. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务传播行为和事务只读属性; 15. 事务方法被同一个类中的其他事务方法调用,但使用了不同的事务传播行为和事务回滚规则。\[3\] #### 引用[.reference_title] - *1* *2* [Spring事务不生效原因解决方案](https://blog.csdn.net/secretdaixin/article/details/127805326)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Spring事务不生效原因](https://blog.csdn.net/t194978/article/details/123942493)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值