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

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

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

目录

1.service没有被 Spring 管理

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

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

4.多线程调用

5.异常类型错误

6.非public方法修饰

7.方法用final修饰

8.方法用static修饰

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


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修饰


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

选用了数据库引擎不支持事务的存储引擎。比如mysql中选用MyISAM

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值