Transactional注解详解

@Transactional注解可以作用于接口、接口方法、类以及类方法上 1. 当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性 2. 当作用在方法级别时会覆盖类级别的定义 3. 当作用在接口和接口方法时则只有在使用基于接口的代理时它才会生效,也就是JDK动态代理,而不是Cglib代理 4. 当在 protected、private 或者默认可见性的方法上使用 @Transactional 注解时是不会生效的,也不会抛出任何异常 5. 默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰

@Transactional作用范围

@Transactional 可以作用在接、类、类方法。

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效

@Transactional注解的可用属性

1.readOnly

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false

2.rollbackFor

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如: 1. 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 2. 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, BusnessException.class})

3.rollbackForClassName

该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如: 1. 指定单一异常类名称:@Transactional(rollbackForClassName=“RuntimeException”) 2. 指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,“BusnessException”})

4.noRollbackFor

该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚

5.noRollbackForClassName

抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

6.timeout

该属性用于设置事务的超时秒数,默认值为-1表示永不超时

7.propagation

该属性用于设置事务的传播行为 。默认值为:@Transactional(propagation=Propagation.REQUIRED)

  • @Transactional(propagation=Propagation.REQUIRED) 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务(默认)。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )
  • @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务,以非事务的方式运行,如果当前存在事务,暂停当前的事务。
  • @Transactional(propagation=Propagation.REQUIRES_NEW) 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务.( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
  • @Transactional(propagation=Propagation.MANDATORY) 必须在一个已有的事务中执行,否则抛出异常
  • @Transactional(propagation=Propagation.NEVER) 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
  • @Transactional(propagation=Propagation.SUPPORTS) 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务

@Transactional(propagation=Propagation.NESTED) :和 Propagation.REQUIRED 效果一样。

8.isolation属性

isolation :事务的隔离级别,默认值为 Isolation.DEFAULT

  • Isolation.DEFAULT:使用底层数据库默认的隔离级别。
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE

事务隔离级别介绍:

  • @Transactional(isolation = Isolation.READ_UNCOMMITTED)读取未提交数据(会出现脏读, 不可重复读) 基本不使用
  • @Transactional(isolation = Isolation.READ_COMMITTED)读取已提交数据(会出现不可重复读和幻读)
  • @Transactional(isolation = Isolation.REPEATABLE_READ)可重复读(会出现幻读)
  • @Transactional(isolation = Isolation.SERIALIZABLE)串行化
  • 什么是脏读、幻读、不可重复读?

脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据. 相反, “可重复读”在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据
其中MySQL默认使用的隔离级别为REPEATABLE_READ、Oracle的为READ_COMMITTED

Spring事务失效场景

事务失效场景

1.service类未被Spring管理

//@Service (注释了@Service)
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper UserMapper;
    
     @Autowired
    private UserFlowMapper UserFlowMapper;

    @Transactional
    public void addUser(User User) {
        //保存User实体数据库记录
        UserMapper.save(User);
        //保存User数据库记录
        UserFlowMapper.saveFlow(buildFlowByUser(User));
    }
}
  • 事务不生效的原因 :上面例子中, @Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。但是@Service被注释后,你的service类都不是spring管理的,那怎么创建代理类来支持事务呢
  • 解决方案 :加上@Service注解。

2.没有在Spring配置文件中启用事务管理器

@Configuration
public class AppConfig {
    // 没有配置事务管理器
}

@Service
public class MyService {
    @Transactional
    public void doSomething() {
        // ...
    }
}
  • 事务不生效的原因 :没有在AppConfig中配置事务管理器,因此Spring无法创建事务代理对象,导致事务不生效。即使在MyService中添加了@Transactional注解,该方法也不会被Spring管理的事务代理拦截。
  • 解决方案 :为了解决这个问题,应该在AppConfig中配置一个事务管器。例如:
@Configuration
public class AppConfig {
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

@Service
public class MyService {
    @Transactional
    public void doSomething() {
        // ...
    }
}

如果是Spring Boot项目,它默认会自动配置事务管理器并开启事务支持。

3. 事务方法被final、static关键字修饰

@Service
public class UserServiceImpl  {

    @Autowired
    private UserMapper UserMapper;
    
    @Autowired
    private UserFlowMapper UserFlowMapper;

    @Transactional
    public final void addUser(User User) {
         //保存User实体数据库记录
        UserMapper.save(User);
        //保存User流水数据库记录
        UserFlowMapper.saveFlow(buildFlowByUser(User));
    }
}
  • 事务不生效的原因 :如果一个方法被声明为final或者static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。
  • 解决方案addUser事务方法 不要用final修饰或者static修饰。

4. 同一个类中,方法内部调用

Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper UserMapper;
    
    @Autowired
    private UserFlowMapper UserFlowMapper;
    
    public void addUser(User User){
     // 调用内部的事务方法
     this.executeAddUser(User);
   }

    @Transactional
    public void executeAddUser(User User) {
        UserMapper.save(User);
        UserFlowMapper.saveFlow(buildFlowByUser(User));
    }
}
  • 事务不生效的原因 : 事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用 。即以上代码,调用目标executeAddUser方法不是通过代理类进行的,因此事务不生效。
  • 解决方案 :可以新建多一个类,让这两个方法分开,分别在不同的类中。如下:
@Service
public class UserExecuteServiceImpl implements UserExecuteService {

    @Autowired
    private UserMapper UserMapper;
    @Autowired
    private UserFlowMapper UserFlowMapper;
    
    @Transactional
    public void executeAddUser(User User) {
        UserMapper.save(User);
        UserFlowMapper.saveFlow(buildFlowByUser(User));
    }
}

@Service
public class UserAddServiceImpl implements UserAddService {

    @Autowired
    private UserExecuteService UserExecuteService;
    
    public void addUser(User user){
     UserExecuteService.executeAddUser(user);
   }
}

当然,有时候你也可以在该 Service 类中注入自己,或者通过AopContext.currentProxy()获取代理对象。

5.方法的访问权限不是public

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper UserMapper;
    
    @Autowired
    private UserFlowMapper UserFlowMapper;

    @Transactional
    private void addUser(User User) {
        UserMapper.save(User);
        UserFlowMapper.saveFlow(buildFlowByUser(User));
    }
}
  • 事务不生效的原因spring事务方法addUser的访问权限不是public,所以事务就不生效啦,因为Spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,也就是这时事务属性不存在了。大家可以看下AbstractFallbackTransactionAttributeSource的源码:

参考文档

1

您的鼓励,是我最大的动力
微信

微信

支付宝

支付宝


Transactional注解详解
http://www.zibbo.xyz/king/e3ca066d.html
作者
King
发布于
2023年8月1日
许可协议