[Spring]Spring声明式事务总结

文章目录

  • 1、介绍
  • 2、Spring事务的隔离级别
  • 3、事务的传播行为
  • 4、@Transactional注解包含的属性
  • 5、使用
  • 6、@Transactional失效场景

1、介绍

声明式事务管理是建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

优点:

  • 不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。

不足:

  • 最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

2、Spring事务的隔离级别

Spring 的接口 TransactionDefinition 中定义了表示隔离级别的常量,当然其实主要还是对应数据库的事务隔离级别:

ISOLATION_DEFAULT:使用后端数据库默认的隔离界别,MySQL 默认可重复读,Oracle 默认读已提交。
ISOLATION_READ_UNCOMMITTED:读未提交(Mysql:READ-UNCOMMITTED)
ISOLATION_READ_COMMITTED:读已提交(Mysql:READ-COMMITTED)
ISOLATION_REPEATABLE_READ:可重复读(Mysql:REPEATABLE-READ)
ISOLATION_SERIALIZABLE:串行化(Mysql:SERIALIZABLE)

对上面内容陌生的可以先看看这篇数据库事务总结

3、事务的传播行为

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

4、@Transactional注解包含的属性

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum:Propagation 可选的事务传播行为设置
isolation enum:Isolation 可选的事务传播行为设置
readOnly boolean 读写或只读事务,默认读写
timeout int 事务超时时间设置
rollbackFor Class对象数组必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

5、使用

  1. 在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。
  2. @Transactional 注解只能应用到 public 可见度的方法上。

6、@Transactional失效场景

  1. @Transactional 应用在非 public 修饰的方法上
    如果 Transactional 注解应用在非 public 修饰的方法上,Transactional 将会失效。
    是因为在 Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法 或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取@Transactional 的属性配置信息。
protected、private修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错

  1. @Transactional 注解属性 propagation 设置错误(设置的传播类型不支持事务,导致事务失效)
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  1. @Transactional 注解属性 rollbackFor 设置错误
    rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认如果抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。
    例如:运行过程中报了已检查异常(checked)比如IOException,数据库操作还是会提交的,但是如果我们需要它进行事务回滚,这时候可以在方法上通过修改@Transactional这个注解的rollbackFor=Exception.class来修改它的行为,既使你出现了checked这种例外,那么它也会对事务进行回滚

若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

  1. mysql使用myIsam引擎不支持事务,修改成innodb引擎

  2. @Transactional注解所在的类没有被Spring管理,导致事务失效
    解决办法是:添加@Service注解或者其他能注册成spring bean的注解

  3. catch异常后,没有再次抛出异常,导致事务失效(异常被"吃"了)
    如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new
    RuntimeException异常或者Spring支持的异常类型,则事务不会回滚。需要cache捕获异常后,再次抛出支持Spring事务的异常,事务才会正常执行。

  4. 方法对同一个类中其他方法调用,导致@Transactional 失效
    AOP使用的是动态代理的机制,它会给类生成一个代理类,事务的相关操作都在代理类上完成。内部方式使用this调用方式时,使用的是实例调用,并没有通过代理类调用方法,所以会导致事务失效。

例如:

@Service
public class IUserServiceImpl implements IUserService{

	@Transactional(rollbackFor = RuntimeException.class)
    public void insertUser(){
    	//其他处理
        this.insertUserDept();
    }
    @Transactional(rollbackFor = RuntimeException.class)
    public void insertUserDept(){
        userMapper.insertUserDept();
    }
}

解决方法是:

  1. 在该service实现类中通过@Resource注入自身Bean,然后通过调用自身bean,从而实现使用AOP代理操作。
@Service
public class IUserServiceImpl implements IUserService{
	@Resource
    private IUserService userService;

	@Transactional(rollbackFor = RuntimeException.class)
    public void insertUser(){
    	//其他处理
        userService.insertUserDept();
    }
    @Transactional(rollbackFor = RuntimeException.class)
    public void insertUserDept(){
        userMapper.insertUserDept();
    }
}

为什么自己注入自己不会导致死循环?

你可能感兴趣的:(spring,java,后端)