java注解@Transactional事务类内调用不生效问题及解决办法

@Transactional 内部调用例子

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。
若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有 @Transactional 注解的方法的事务被忽略,不会发生回滚

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class A{
   
  public void action(){
    dosome();
  }
   
  @Transactional
  public void dosome(){
    doa.insert(new Object());
  }
}

如上代码, 在方法 dosome() 中抛出异常时, 数据操作不会回滚

解决方案

思路: 强制使用 AspectJ 对方法进行切面

Springboot 引入 AspectJ 切面

pom.xml 中添加 AspectJ:

1
2
3
4
5
6
7
8
9
10
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.5</version>
</dependency>

启动类中添加 @EnableAspectJAutoProxy(exposeProxy = true)

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class DonngPartsApplication {
 
  public static void main(String[] args) {
    SpringApplication.run(DonngPartsApplication.class, args);
  }
 
}

注意: exposeProxy = true 若不添加, 则会报:

java.lang.IllegalStateException:

Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available,

and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.

代码中 ((A) AopContext.currentProxy()).dosome()

修改为如下代码, 事务就生效啦

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class A{
   
  public void action(){
    ((A) AopContext.currentProxy()).dosome();
  }
   
  @Transactional
  public void dosome(){
    doa.insert(new Object());
  }
}

@Transactional 进阶

1. @Transactional 注解的属性信息

 

属性描述
name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器
propagation 事务的传播行为,默认值为 REQUIRED
isolation 事务的隔离度,默认值采用 DEFAULT
timeout 事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务
read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true
rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔
no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务

2. propagation 传播行为

  • REQUIRED:如果有事务, 那么加入事务, 没有的话新建一个 (默认情况下)
  • NOT_SUPPORTED:容器不为这个方法开启事务
  • REQUIRES_NEW:不管是否存在事务, 都创建一个新的事务, 原来的挂起, 新的执行完毕, 继续执行老的事务
  • MANDATORY:必须在一个已有的事务中执行, 否则抛出异常
  • NEVER:必须在一个没有的事务中执行, 否则抛出异常 (与 MANDATORY 相反)
  • SUPPORTS:如果其他 bean 调用这个方法, 在其他 bean 中声明事务, 那就用事务. 如果其他 bean 没有声明事务, 那就不用事务.
  • NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与 PROPAGATION_REQUIRED 类似的操作。

3. 事物超时设置
@Transactional(timeout=30) // 默认是 30 秒

4. 事务隔离级别 isolation

  • READ_UNCOMMITTED:读取未提交数据 (会出现脏读, 不可重复读) 基本不使用
  • READ_COMMITTED:读取已提交数据 (会出现不可重复读和幻读)
  • REPEATABLE_READ:可重复读 (会出现幻读)
  • SERIALIZABLE:串行化

注意

@Transactional 只能被应用到 public 方法上

仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据

 

/**
     * REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
     * REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生 (不可重复读)
     * readOnly:不允许只读 rollbackFor: 回滚策略为 Exception 出现异常之后
     * TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 函数内捕获异常时需要来设置事务回滚状态
     * Spring Transactional 一直是 RD 的事务神器,但是如果用不好,反会伤了自己。下面总结 @Transactional 经常遇到的几个场景: 
        @Transactional 加于 private 方法, 无效
        @Transactional 加于未加入接口的 public 方法, 再通过普通接口方法调用, 无效
        @Transactional 加于接口方法, 无论下面调用的是 private 或 public 方法, 都有效
        @Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效
        @Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效
        @Transactional 加于接口方法后, 被它类的接口方法调用, 有效
        @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效
     */