这几个事务失效的坑,你踩过吗

在这里插入图片描述

平时开发中会遇到需要加事务的情况,常用的是使用注解@Transactional,但是有时发现明明加了,但没有生效,为什么呢?

今天就来讲解几种平时会遇到的事务失效的场景,避免以后踩坑。点了在看就是学会了,哈哈哈。

一、事务失效场景

1、方法的访问类型不是 public

    @Transactional
    private void save() {
        User user = getUser();
        userMapper.insert(user);
        this.teachSave();
    }

我们可以看到 save 方法使用了 private,这是不太容易发现的场景。

spring 要求代理的方法必须是 public 修饰,通过以下源码就可以发现,要求是 public,不是 public 的返回 null,也就是不支持事务。图片

2、方法内部调用

比如有两个方法,test1 和 test2,test1 调用 test2,会有以下 4 种场景:

  • test1 有事务,test2 无事务,事务生效

  • test1 有事务,test2 有事务,事务生效

  • test1 无事务,test2 有事务,事务不生效

  • test1 无事务,test2 无事务,事务不生效

2.1、test1 有事务,test2 无事务

    @Transactional
    public void test1() {
        User user = getUser();
        userMapper.insert(user);
        this.test2();
    }

    public void test2() {
        Teacher t = getTeacher();
        teacherMapper.insert(t);
        int a = 1 / 0;
    }

test1 方法的事务生效时,那么对 user 和 teacher 表的插入操作,同属于一个事务,要么都成功,要么都失败

2.2、test1 有事务,test2 有事务

    @Transactional
    public void test1() {
        User user = getUser();
        userMapper.insert(user);
        this.test2();
    }
    @Transactional
    public void test2() {
        Teacher t = getTeacher();
        teacherMapper.insert(t);
        int a = 1 / 0;
    }

test1 方法的事务生效时,那么对 user 和 teacher 表的插入操作,test2 方法的事务生不生效,结果都是一样,效果同 2.1,同属于一个事务,要么都成功,要么都失败。

2.3、test1 无事务,test2 有事务

    public void test1() {
        User user = getUser();
        userMapper.insert(user);
        this.test2();
    }

    @Transactional
    public void test2() {
        Teacher t = getTeacher();
        teacherMapper.insert(t);
        int a = 1 / 0;
    }

对 user 和 teacher 表的插入操作,通过直接调用 this 对象的方法,所以 test2 方法的事务是失效的,不能使用 this.的方式调用。

2.4、test1 无事务,test2 无事务

    public void test1() {
        User user = getUser();
        userMapper.insert(user);
        this.test2();
    }

    public void test2() {
        Teacher t = getTeacher();
        teacherMapper.insert(t);
        int a = 1 / 0;
    }

这个场景比较好理解,都没加事务,所以对 user 和 teacher 的操作,没有事务。

通过上面的 4 种场景,可以知道通过 this 调用内部方法,事务是不生效的,那怎样才能让事务生效呢?

  • 将方法 test2 放在另一个 serviceB 里,通过 serviceB 调用 test2,这样事务就可以生效
@Servcie
public class ServiceA {
   @Autowired
   prvate ServiceB serviceB;

    public void test1() {
        serviceB.test2();
    }

 @Servcie
 public class ServiceB {

    @Transactional(rollbackFor=Exception.class)
    public void test2() {
        addUser();
        addTeacher();
    }

 }

  • 注入自己
@Servcie
public class ServiceA {
   @Autowired
   prvate ServiceA serviceA;

    public void test1() {
        serviceA.test2();
    }
    @Transactional(rollbackFor=Exception.class)
    public void test2() {
        addUser();
        addTeacher();
    }
}

注入自己,通过自己调用 test2,事务是生效的

  • 使用 AopContext.currentProxy()获取代理对象
@Servcie
public class ServiceA {
  public void test1() {
        ((ServiceA)AopContext.currentProxy()).test2();
    }
    @Transactional(rollbackFor=Exception.class)
    public void test2() {
        addUser();
        addTeacher();
    }
}

在 serviceA 类中使用 AopContext.currentProxy()获取代理对象,事务生效。

3、异常类型错误

@Service
public class ServiceA {

    @Transactional
    public void add(UserModel userModel) throws Exception {
        try {
             addUser();
             addTeacher();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception(e);
        }
    }
}

可以看到发生异常时,被 try-catch 补获并抛出 exception 异常,但是因为 spring 事务,默认情况下只会回滚 RuntimeException(运行时异常)和 Error(错误),对于普通的 Exception(非运行时异常),它不会回滚。所以一般都要求 Transactional 指定 rollbackfor。

4、抛出的异常被吃了

@Service
public class ServiceA {

    @Transactional(rollbackFor=Exception.class)
    public void add(UserModel userModel) throws Exception {
        try {
             addUser();
             addTeacher();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

上面异常被 try-catch 吃掉了,且没有往外抛异常,所以这个时候事务是无法回滚。

5、@Transactional 的 propagation 参数设置不对

该参数的作用是指定事务的传播特性,spring 目前支持 7 种传播特性:

  • REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。

  • SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。

  • MANDATORY 如果当前上下文中存在事务,否则抛出异常。

  • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

  • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。

  • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。

  • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

比如以下代码,设置为 never,这样在有事务情况下,会抛出异常

    @Transactional(propagation=Propagation.NEVER)
    public void test2() {
        addUser();
        addTeacher();
    }

目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。

二、事务提交方式

1、声明式事务-自动提交

   @Transactional(rollbackFor=Exception.class)
   public void save(final User user) {
         queryUser();
         queryTeacher();
         addUser();
         addTeacher();
   }

使用注解 Transactional,将整个方法包裹在事务中,但是其实 query 查询的方法不需要事务,真正需要事务的只有 addUser 和 addTeacher,造成了大事务。

2、编程式事务-手动提交

   public void save(final User user) {
         queryUser();
         queryTeacher();
         transactionTemplate.execute((status) => {
            addUser();
            addTeacher();
            return Boolean.TRUE;
         })

在需要提交事务的地方,手动进行提交,缩小事务的范围。

所以建议使用编程式事务,更小粒度的控制事务的范围。


关注公众号:臻大虾,分享更多java后端技术干货
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值