Spring事务Thransctionl总结:

1、@Transactional 注解的参数

在声明事务中,我们只需要和注解 @Transactional 打交道,所以我们有必要来深入理解一下这个注解中的参数配置。

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
    
    // 事务管理器、暂时先忽略它,我们也不会去修改这个参数的值
    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};
    
    // 事务传播行为
    Propagation propagation() default Propagation.REQUIRED;
    
    // 事务隔离级别
    Isolation isolation() default Isolation.DEFAULT;
    
    // 事务超时时间 -1,为永久不超时, 单位是秒
    int timeout() default -1;
    
    // 事务超时时间,可以设置单位,比如 timeoutString = "30s"
    String timeoutString() default "";
    
    // 是否只读事务
    boolean readOnly() default false;
    
    // 对哪些异常进行回滚
    Class<? extends Throwable>[] rollbackFor() default {};
    
    // 对哪些异常进行回滚【异常全限定名】
    String[] rollbackForClassName() default {};
    
    // 对哪些异常不回滚
    Class<? extends Throwable>[] noRollbackFor() default {};
    
    // 对哪些异常不回滚【异常全限定名】
    String[] noRollbackForClassName() default {};
}

rollbackFor和rollbackForClassName的区别,直接来看使用方式。 最好使用rollbackFor 可以在编译的时候就帮我买检查是不是对的。

@Transactional(rollbackFor = Exception.class, rollbackForClassName = {"java.lang.Exception"})

@Transactional 注解的参数虽然多,但绝大部分都很好理解。这里主要是来说两个重要且不好理解的参数propagationisolation

1.1、propagation (事务传播行为)

事务的传播行为是指:当前事务方法被调用的时候,需要做什么样的操作。它的配置如下:

***(小写方便阅读)***描述
REQUIRED*(required)*默认值如果当前没有事务,则创建一个新的事务,并将当前方法作为事务的起点。**如果当前已经存在事务,则加入到当前事务中,成为当前事务的一部分。**当前事务的提交和回滚都将影响到该方法。
REQUIRES_NEW (requires_new)无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起,并启动一个新的事务。当前方法独立于外部事务运行,它有自己的事务边界。
SUPPORTS*(supports)***如果当前存在事务,则加入到当前事务中,成为当前事务的一部分。**如果当前没有事务,则以非事务方式执行。支持当前事务的执行,但不强制要求存在事务。
NOT_SUPPORTED (not_supported)以非事务方式执行操作。如果当前存在事务,则将其挂起。该方法在一个没有事务的环境中执行。
NEVER*(never)*以非事务方式执行操作。如果当前存在事务,则抛出异常,表示不允许在事务中执行该方法。
MANDATORY*(mandatory)*要求当前存在事务,否则抛出异常。该方法必须在一个已经存在的事务中被调用。
NESTED (nested)如果当前存在事务,则在嵌套事务中执行。如果当前没有事务,则行为类似于 REQUIRED,创建一个新的事务。

存在事务的时候REQUIRED和NESTED的区别:REQUIRED 是加入当前事务,成为当前事务的一部分,NESTED 是生成嵌套事务,本质上是两个事务。(具体区别下面实践演示)

1.2、isolation(事务隔离级别)

其实就是我们之前学习数据库时候的数据库隔离级别了。

***(小写方便阅读)***描述
DEFAULT (default)默认的,看当前数据库默认的隔离级别是什么
READ_UNCOMMITTED (read_uncommitted)读未提交
READ_COMMITTED (read_committed)读已提交
REPEATABLE_READ (repeatable_read)可重复读
SERIALIZABLE (serializable)序列化

2、@Thransctionl 使用方式:

  • 当我们的方法上面加了@Thransctionl 注解的时候,但是在我们的方法里面直接抛出 throw new Exception(“”) 事,事务注解是不起作用的,控制不了事务的;

  • 因为在@Thransctionl 注解中有一个 "rollbackFor"的属性:

    Class <? extends Throwable>[]rollbackFor( ) default{}
    

    而rollbackFor是针对什么样的异常回滚呢?

    在官方的文档中指出只会对运行异常时异常(RuntimeException)和错误(Error)进行回滚;

    当我们抛出Exception,受检异常是不能回滚的;

  • 所以当我们在业务代码中要抛出异常时,不能抛出这一类的受检异常,而是运行异常,或者呢在@Thransctionl 注解中声明一下“rollbackFor” 什么样的异常要回

    @Transactional(rollbackFor = Exception.class)
    
  • 或者我们抛出一个自定义异常,该自定义异常它又继承RuntimeException 运行时异常

    throw new CustomException(“数据有误,需要回滚!”)
    

    这样就不用在方法上面声明这个异常需要回滚了 ;

3、@Thransctionl 使用场景:

  • @Transactional注解 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

  • 虽然@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional注解应该只被应用到 public 方法上,这是由Spring AOP的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

  • 需要注意的是,在同一个类中调用被 @Transactional 注解修饰的方法时,事务处理可能会失效,因为 Spring 会在运行时为该类创建一个代理对象,而在同一个类中调用方法时,是不会经过代理对象的。如果需要在同一个类中调用被 @Transactional 注解修饰的方法,可以将该方法抽取到另一个类中,并通过依赖注入的方式调用。也可以先获取到本类的代理对象再调用内部方法。

3.2 函数之间相互调用

关于有@Transactional的函数之间调用,会产生什么情况。这里咱们通过几个例子来说明。

3.2.1 同一个类中函数相互调用

同一个类AClass中,有两个函数aFunction、aInnerFunction。aFunction调用aInnerFunction。而且aFunction函数会被外部调用。

情况1:

aFunction添加了@Transactional注解,aInnerFunction函数没有添加。aInnerFunction抛异常。

public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}

结果:两个函数操作的数据都会回滚。
情况2:

两个函数都添加了@Transactional注解。aInnerFunction抛异常。

public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}

结果:同第一种情况一样,两个函数对数据库操作都会回滚。因为同一个类中函数相互调用的时候,内部函数添加@Transactional注解无效。@Transactional注解只有外部调用才有效。
情况3:

aFunction不添加注解,aInnerFunction添加注解。aInnerFunction抛异常。

public class AClass {
 
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    @Transactional(rollbackFor = Exception.class)
    protected void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}

结果:两个函数对数据库的操作都不会回滚。因为内部函数@Transactional注解添加和没添加一样。
情况4:
aFunction添加了@Transactional注解,aInnerFunction函数没有添加。aInnerFunction抛异常,不过在aFunction里面把异常抓出来了。
public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}

结果:两个函数里面的数据库操作都成功。事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。(当然了要看你添加的@Transactional注解有没有效)
3.2.1. 不同类中函数相互调用

两个类AClass、BClass。AClass类有aFunction、BClass类有bFunction。AClass类aFunction调用BClass类bFunction。最终会在外部调用AClass类的aFunction。

情况1:

aFunction添加注解,bFunction不添加注解。bFunction抛异常。

@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        bClass.bFunction();
    }
 
}

@Service()
public class BClass {
 
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}

结果:两个函数对数据库的操作都回滚了。

情况2:

aFunction、bFunction两个函数都添加注解,bFunction抛异常。

@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        bClass.bFunction();
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}

结果:两个函数对数据库的操作都回滚了。两个函数里面用的还是同一个事务。这种情况下,你可以认为事务rollback了两次。两个函数都有异常。

情况3:

aFunction、bFunction两个函数都添加注解,bFunction抛异常。aFunction抓出异常。

@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            bClass.bFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}

结果:两个函数数据库操作都没成功。而且还抛异常了。org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。看打印出来的解释也很好理解把。咱们也可以这么理解,两个函数用的是同一个事务。bFunction函数抛了异常,调了事务的rollback函数。事务被标记了只能rollback了。程序继续执行,aFunction函数里面把异常给抓出来了,这个时候aFunction函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数。而之前已经标记了事务只能rollback-only(以为是同一个事务)。直接就抛异常了,不让调了。

情况4:

aFunction、bFunction两个函数都添加注解,bFunction抛异常。aFunction抓出异常。这里要注意bFunction函数@Transactional注解我们是有变化的,加了一个参数propagation = Propagation.REQUIRES_NEW,控制事务的传播行为。表明是一个新的事务。其实咱们情况3就是来解决情况2的问题的。

@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            bClass.bFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}

结果:bFunction函数里面的操作回滚了,aFunction里面的操作成功了。有了前面情况2的理解。这种情况也很好解释。两个函数不是同一个事务了。

1.3.4 同类内方法调用如何使事务生效

上面示例中,在同一个类中调用被 @Transactional 注解修饰的方法时,事务处理可能会失效,因为 Spring 会在运行时为该类创建一个代理对象,而在同一个类中调用方法时,是不会经过代理对象的。如果需要在同一个类中调用被 @Transactional 注解修饰的方法,可以将该方法抽取到另一个类中,并通过依赖注入的方式调用。也可以先获取到本类的代理对象再调用内部方法。

方法1:

使用 AopContext 类获取代理对象

需要注意的是,使用 AopContext 类获取代理对象可能会带来安全风险,因此默认情况下是禁用的。如果需要启用该功能,可以在配置文件中设置 expose-proxy 属性为 true。例如,在 Spring Boot 中可以通过以下方式设置:spring.aop.proxy-target-class: true

@Service
public class UserService {

   @Autowired
   private UserDao userDao;

   @Transactional(propagation = Propagation.REQUIRED)
   public void updateUser(User user) {
       // 获取当前对象的代理对象
       UserService userService = (UserService) AopContext.currentProxy();

       // 调用内部方法,启用事务处理
       userService.updateUserDetail(user.getUserId(), user.getUserDetail());
   }

   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void updateUserDetail(Long userId, UserDetail userDetail) {
       userDao.updateUserDetail(userId, userDetail);
   }
}

方法2:

使用 beanFactory.getBean 类获取代理对象

@Service
public class UserService {

   @Autowired
   private BeanFactory beanFactory;

   @Autowired
   private UserDao userDao;

   @Transactional(propagation = Propagation.REQUIRED)
   public void updateUser(User user) {
       // 获取当前对象的代理对象
       UserService userService = beanFactory.getBean(UserService.class);

       // 调用内部方法,启用事务处理
       userService.updateUserDetail(user.getUserId(), user.getUserDetail());
   }

   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void updateUserDetail(Long userId, UserDetail userDetail) {
       userDao.updateUserDetail(userId, userDetail);
   }
}

在上面的代码中,updateUser() 方法通过 beanFactory.getBean(UserService.class) 方法获取当前对象的代理对象,然后调用内部方法 updateUserDetail(),从而启用事务处理。需要注意的是,在使用 getBean() 方法获取当前对象的代理对象时,要求该类必须是一个 Spring 管理的 Bean,否则会抛出异常。

总结:

要知道@Transactional注解里面每个属性的含义。@Transactional注解属性就是来控制事务属性的。通过这些属性来生成事务。
要明确我们添加的@Transactional注解会不会起作用。@Transactional注解在外部调用的函数上才有效果,内部调用的函数添加无效,要切记。这是由AOP的特性决定的。
要明确事务的作用范围,有@Transactional的函数调用有@Transactional的函数的时候,进入第二个函数的时候是新的事务,还是沿用之前的事务。稍不注意就会抛UnexpectedRollbackException异常。

  • 33
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值