事务失效的情况和原因分析

182 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

平时我们开发需要满足事务时,我们就会想到@Transactional注解,但是添加这个注解后也可能也不会满足我们的需求,甚至事务不生效。下面我就分析下事务失效的原因及事务如何使用。

下面开始介绍失效原因及解决方案。

1.service没有被 Spring 管理

// @Service 必须要添加注解,被spring管理
public class TestTransactionalServiceImpl implements TestTransactionalService{
       
        @Override
        @Transactional
        public void testTransactional() {
        }
}

解决方案:添加@Service注解,使得事务类被spring管理

2.异常被捕捉了(异常被吃了)

@Transactional
public void testTransactional() {
    try {
        // 修改数据===1===
        AdCourse adCourse = new AdCourse();
        adCourse.setId(5203314);
        adCourse.setName("计算机科学");
        int i = adCourseMapper.updateById(adCourse);

        // 修改数据===2===
        adCourse.setId(5219314);
        adCourse.setName("数据2");
        int result = adCourseMapper.updateById(adCourse);

        // 手动制造异常
        result = 1/0;
    } catch(Exception e){  // 本质:异常被捕捉,事务检测不到有错误的存在
        e.printStackTrace();
        log.error("程序出现异常了");
    }
}

本质:异常被捕捉,事务检测不到有错误的存在

解决方案1:把异常再次抛出 throw e;

} catch(Exception e){
    e.printStackTrace();
    log.error("程序出现异常了");
    throw e;  // 把异常再次抛出
}
解决方案2:在捕捉的异常中,手动添加回滚

} catch(Exception e){
    e.printStackTrace();
    log.error("程序出现异常了");
    // 手动回滚
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
解决方案3(推荐):将业务代码单独抽出,在抽出的方法上加事务

@Override
public void testTransactional() {
    try {
        testRollback();
    } catch(Exception e){
        e.printStackTrace();
        log.error("程序出现异常了");
    }
}

@Transactional
public void testRollback(){
    // 修改数据===1===
    AdCourse adCourse = new AdCourse();
    adCourse.setId(5203314);
    adCourse.setName("计算机科学");
    int i = adCourseMapper.updateById(adCourse);

    // 修改数据===2===
    adCourse.setId(5219314);
    adCourse.setName("数据2");
    int result = adCourseMapper.updateById(adCourse);

    // 手动制造异常
    result = 1/0;
}

3.调用本类方法,事务失效

@Override
public void testTransactional() {
    try {
        testRollback();
    } catch(Exception e){
        e.printStackTrace();
    }
}

@Transactional  // 事务未生效
public void testRollback(){
    // 修改数据===1===
    AdCourse adCourse = new AdCourse();
    adCourse.setId(5203314);
    adCourse.setName("计算机科学");
    int i = adCourseMapper.updateById(adCourse);

    // 修改数据===2===
    adCourse.setId(5219314);
    adCourse.setName("数据2");
    int result = adCourseMapper.updateById(adCourse);

    // 手动抛出异常
    result = 1/0;
}

本质原因:因为发生了自身调用,调类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效。

解决方案:需要在testTransactional()方法上加@Transactional注解,testRollback()方法可以不用加(本类方法加了也不起作用)。

@Override
@Transactional
public void testTransactional() {
    try {
        testRollback();
    } catch(Exception e){
        e.printStackTrace();
    }
}

// @Transactional(propagation = Propagation.REQUIRES_NEW) 加了也不起作用
public void testRollback(){
    // 修改数据===1===
    AdCourse adCourse = new AdCourse();
    adCourse.setId(5203314);
    adCourse.setName("计算机科学");
    int i = adCourseMapper.updateById(adCourse);

    // 修改数据===2===
    adCourse.setId(5219314);
    adCourse.setName("数据2");
    int result = adCourseMapper.updateById(adCourse);

    // 手动抛出异常
    result = 1/0;
}

4.多线程调用

@Override
@Transactional
public void testTransactional() {
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<Boolean> future = executorService.submit(() -> {
        // 修改数据===1===
        AdCourse adCourse = new AdCourse();
        adCourse.setId(5203314);
        adCourse.setName("计算机科学");
        int i = adCourseMapper.updateById(adCourse);

        // 修改数据===2===
        adCourse.setId(5219314);
        adCourse.setName("数据2");
        int result = adCourseMapper.updateById(adCourse);

        // 手动抛出异常
        result = 1/0;
        return true;
    });
}

本质:spring的事务是通过数据库连接来实现,数据库连接spring是放在threadLocal里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务。

解决方案:将业务代码单独抽出,在抽出的方法上加事务

5.异常类型错误

   // @Transactional(rollbackFor = FdException.class)
public void testTransactional() {
    try {
        // 修改数据===1===
        AdCourse adCourse = new AdCourse();
        adCourse.setId(5203314);
        adCourse.setName("计算机科学");
        int i = adCourseMapper.updateById(adCourse);

        // 修改数据===2===
        adCourse.setId(5219314);
        adCourse.setName("数据2");
        int result = adCourseMapper.updateById(adCourse);

    }catch(FdException e){
        throw e;
    }
} 

这样事务是不生效的,因为默认回滚的是:RuntimeException及其子类,如果你想触发其他异常的回滚,需要在注解上配置一下:(rollbackFor = FdException.class)

6.非public方法修饰

    @Transactional
protected void testTransactional() {
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<Boolean> future = executorService.submit(() -> {
        // 修改数据===1===
        AdCourse adCourse = new AdCourse();
        adCourse.setId(5203314);
        adCourse.setName("计算机科学");
        int i = adCourseMapper.updateById(adCourse);

        // 修改数据===2===
        adCourse.setId(5219314);
        adCourse.setName("数据2");
        int result = adCourseMapper.updateById(adCourse);

        // 手动抛出异常
        result = 1/0;
        return true;
    });
}

spring事务默认生效的方法权限都必须是public修饰

7.方法用final修饰

    @Transactional
public final void testRollback(){
    // 修改数据===1===
    AdCourse adCourse = new AdCourse();
    adCourse.setId(5203314);
    adCourse.setName("计算机科学");
    int i = adCourseMapper.updateById(adCourse);

    // 修改数据===2===
    adCourse.setId(5219314);
    adCourse.setName("数据2");
    int result = adCourseMapper.updateById(adCourse);

    // 手动抛出异常
    result = 1/0;
}
原因:因为spring事务是用动态代理实现,因此如果方法使用了final修饰,则代理类无法对目标方法进行重写,所以就不存在事务功能

解决方案:去掉final修饰

8.方法用static修饰

    @Transactional
public static void testRollback(){
    // 修改数据===1===
    AdCourse adCourse = new AdCourse();
    adCourse.setId(5203314);
    adCourse.setName("计算机科学");
    int i = adCourseMapper.updateById(adCourse);

    // 修改数据===2===
    adCourse.setId(5219314);
    adCourse.setName("数据2");
    int result = adCourseMapper.updateById(adCourse);

    // 手动抛出异常
    result = 1/0;
}
原因:因为spring事务是用动态代理实现,因此如果方法使用了static修饰,则代理类无法对目标方法进行重写,所以就不存在事务功能

解决方案:去掉static修饰

原因:因为spring事务是用动态代理实现,因此如果方法使用了static修饰,则代理类无法对目标方法进行重写,所以就不存在事务功能

解决方案:去掉static修饰

​​​​​​​9.选用了数据库引擎不支持事务的存储引擎