快来看看什么情况下Transactional 注解会失效吧

这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战

@Transactional是我们在用Spring时候几乎逃不掉的一个注解,该注解主要用来声明事务。它的实现原理是通过Spring AOP在注解修饰方法的前后织入事务管理的实现语句,所以开发者只需要通过一个注解就能代替一系列繁琐的事务开始、事务关闭等重复性的编码任务。

一起看看,什么情况下@Transactional会失效,别再只会说:“我本地可以啊”~

在同一个类中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typescript复制代码public class A {
    
    public void methodA() {
        methodB();
        
        // 其他操作
    }

    @Transactional
    public void methodB() {
        // 写数据库操作
    }
    
}
// 注意:这里A类用了构造器注入B的实现,构造函数用Lombok的
// @AllArgsConstructor生成。

我们调用的方法A不带注解,所示是直接调用目标对象的方法。当进入目标对象的方法后,执行的上下文已经变成目标对象本身了,因为目标对象的代码是我们自己写的,和事务没有关系,此时你再调用带注解的方法,照样没有事务,只是一个普通的方法调用而已。简单来说,内部调用本类方法,不会再走代理了,所以B的事务不起作用。

看不懂吗?看不懂换个说法~

因为在A方法中调用B方法相当于使用this.B,this代表的是Service类本身,并不是真实的代理Service对象,这种不能实现代理功能。Spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,Spring 会为这个bean动态地生成一个子类(即代理类proxy),proxy是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由proxy来调用的,proxy在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过proxy,而是直接通过原来的那个bean,所以就不会启动transaction,即@Transactional注解无效。

解决方法

  1. 在xxxServiceImpl中,用(xxxService)(AopContext.currentProxy()),获取到xxxService的代理类,再调用事务方法,强行经过代理类,激活事务切面。
1
2
3
4
5
6
7
8
9
10
11
12
typescript复制代码public class test {

@Transactional
public void a() {
(test)(AopContext.currentProxy()).b();
}

@Transactional
public void b() {
// 其他代码
}
}
  1. @Autowired 注入自己来调用方法解决。
  2. 将事务方法放到另一个类中(或者单独开启一层,取名“事务层”)进行调用,即符合了在对象之间调用的条件。还是合理规划好层次关系即可,比如这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
less复制代码@Service
@AllArgsConstructor
public class A {
    
    private B b;
    
    public void methodA() {
        b.methodB();
        // 其他操作
    }
}

@Service
public class B {

    @Transactional
    public void methodB() {
        // 写数据库操作
    }
    
}

@Transactional修饰方法不是public

1
2
3
4
5
6
7
8
typescript复制代码public class TransactionalMistake {
    
    @Transactional
    private void method() {
        // 写数据库操作
    }
    
}

不同的数据源

1
2
3
4
5
6
7
8
9
typescript复制代码public class TransactionalMistake {

    @Transactional
    public void createOrder(Order order) {
        orderRepo1.save(order);
        orderRepo2.save(order);
    }

}

回滚异常配置不正确

默认情况下,仅对RuntimeExceptionError进行回滚。如果不是的它们及它们的子孙异常的话,就不会回滚。

所以,在自定义异常的时候,要做好适当的规划,如果要影响事务回滚,可以定义为RuntimeException的子类;如果不是RuntimeException,但也希望触发回滚,那么可以使用rollbackFor属性来指定要回滚的异常。

1
2
3
4
5
6
7
8
java复制代码public class TransactionalMistake {

    @Transactional(rollbackFor = XXXException.class)
    public void method() throws XXXException {

    }

}

数据库引擎不支持事务

例如漏了一个关键属性的配置:

1
ini复制代码spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

这里的spring.jpa.database-platform配置主要用来设置hibernate使用的方言。这里特地采用了MySQL5InnoDBDialect,主要为了保障在使用Spring Data JPA时候,Hibernate自动创建表的时候使用InnoDB存储引擎,不然就会以默认存储引擎MyISAM来建表,而MyISAM存储引擎是没有事务的。

如果你的事务没有生效,那么可以看看创建的表,是不是使用了MyISAM存储引擎,如果是的话,那就是这个原因了!

参考:

(1)www.cnblogs.com/guobi777/p/…

(2)mp.weixin.qq.com/s/oblTiQnvp…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%