Spring事务源码解析

标注有@Transactional注解的类,会在Spring创建bean时通过cglib进行增强,生成代理类对对应的进行代理

事务注解会通过CglibAopProxy.DynamicAdvisedInterceptor#intercept进行代理,代码如下:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  ...
  try {
    ...
    // 创建cglib方法调用并执行,最终会调用TransactionAspectSupport#invokeWithinTransaction方法,代码如下
    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
    // 处理返回值类型
    retVal = processReturnType(proxy, target, method, retVal);
    // 返回结果
    return retVal;
  }
  finally {
    ...
  }
}

通过TransactionAspectSupport#invokeWithinTransaction方法,对@Transactional注解的配置进行解析,根据配置对应的进行事务处理

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
                                         final InvocationCallback invocation) throws Throwable {
  TransactionAttributeSource tas = getTransactionAttributeSource();
  // 如果txAttr为null,则不是一个事务
  final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
  // 在该方法中会创建对应的TransactionManager事务管理器(包含了数据源等)
  final TransactionManager tm = determineTransactionManager(txAttr);

  ...

  // 将TransactionManager向下转型成PlatformTransactionManager事务管理器
  PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
  // 获取连接点定义(即被事务管理的方法)
  final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

  // 如果是一个事务
  if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
    // 创建并开始事务(AbstractPlatformTransactionManager#startTransaction),获取Connection并进行封装
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

    Object retVal;
    try {
      // 执行真实被代理的方法(若发生异常,会被捕获并处理)
      retVal = invocation.proceedWithInvocation();
    }
    catch (Throwable ex) {
      // 在这里进行数据的回滚操作(默认只会滚回RuntimeException或者Error异常)
      // 在AbstractPlatformTransactionManager#processRollback方法中对配置事务信息进行针对性的事务处理,代码如下
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;
    }
    finally {
      ...
    }
    ...
   
    // 如果方法正常执行,在这里进行数据的提交操作(执行commit操作)
    commitTransactionAfterReturning(txInfo);
    return retVal;
  } else {
    ...
    return result;
  }
}

AbstractPlatformTransactionManager#processRollback:

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
  try {
    boolean unexpectedRollback = unexpected;

    try {
      // 触发beforeCompletion回调
      triggerBeforeCompletion(status);

      // 是否有保存点
      if (status.hasSavepoint()) {
        // 回滚到保存点
        status.rollbackToHeldSavepoint();
      } 
      // 如果是新事物,则回滚事务
      else if (status.isNewTransaction()) {
        // 回滚事务,调用ConnectionImpl#rollbackNoChecks方法,执行rollback的sql语句进行回滚
        doRollback(status);
      } 
      // 如果已经有事务,则加入事务,并标记事务状态,等事务链执行完毕后统一回滚
      else {
        if (status.hasTransaction()) {
          if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
            // 标记事务状态,抛出一个IllegalTransactionStateException异常
            doSetRollbackOnly(status);
          }
        }
        ...
      }
    } catch (RuntimeException | Error ex) {
      // 
      triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
      throw ex;
    }
    // 触发afterCompletion回调
    triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
    ...
  }
  finally {
    cleanupAfterCompletion(status);
  }
}

事务的特性

  1. 原子性(A)
  2. 一致性(C)
  3. 隔离性(I)
  4. 持久性(D)

ACID

Spring中的事务

隔离级别

因事务隔离级别产生的问题如下:

  • 脏读:读到未提交的新记录

    事务1读取到事务2未提交的数据,但是事务2回滚,则事务1读取到了脏数据

  • 不可重复读:前后数据多次读取,读取集内容不一致

    事务1按某条件查询得到结果集,事务2对结果集做修改操作并提交,事务1再次按相同条件查询到的结果集不一致

  • 幻读:前后数据多次读取,读取集数量不一致

    事务1按某条件查询得到结果集,事务2对结果集做新增操作并提交,事务1再次按相同条件查询到的结果集的数量不一致

事务隔离级别 隔离级别描述 可能会出现的问题
读未提交(read uncommit) 一个事务中可读取其他事务未提交的新记录 脏读、不可重复读、幻读
读已提交(read commit) 一个事务可以读取其他事务已提交的新记录 不可重复读、幻读
可重复读(repeatable read) 一个事务可以读取其他事务已提交的新记录,但不能看到其他事务对已有记录的更新 幻读
串行化(serializable) 事务串行执行 保证了事务隔离

在Spring中,定义了对应的4种隔离级别,共5种选择:

  • DEFAULT(默认选中):使用数据库默认的隔离级别
  • READ_UNCOMMITTED:读未提交
  • READ_COMMITTED:读已提交
  • REPEATABLE_READ:可重复读
  • SERIALIZABLE:串行化

传播行为

事务传播行为指的就是事务A方法调用事务B方法时,事务B如何进行

在Spring中,定义了以下7种事务的传播行为:

传播行为 表现
propagation_required(默认传播行为) 没有当前事务,则开启事务;如果有事务,则跟随当前事务
propagation_supports 没有当前事务,则不开启事务;如果有事务,则跟随当前事务
propagation_mandatory 没有当前事务,则抛出异常(方法未执行);如果有事务,则跟随当前事务
propagation_required_new 不管有没有当前事务,都开启新事务,上个事务的提交和回滚不会影响新事务
propagation_not_supports 不管有没有当前事务,都不开启事务
propagation_never 没有当前事务,则不开启事务;如果有事务,则抛出异常(方法未执行,上个事务回滚)
propagation_nested 没有当前事务,则开启事务;如果有事务,则嵌套事务中运行

方法A调用方法B,这里的传播行为指的是方法B中的事务行为

propagation_required_new和propagation_nested的区别:

  • propagation_required_new:会新开启一个事务,该事务与上一个事务完全独立,分开提交和回滚
  • propagation_nested:如果当前事务存在,则会嵌套在当前事务中,与当前事务一起提交。嵌套的意思是,在调用propagation_nested传播行为的方法时,会创建一个saveponit,如果方法发生异常,则回滚到saveponit,再继续往下执行,如果没有异常,则还是在当前事务中提交;如果方法未发生异常,则所有的修改通过当前事务一起提交。

超时时间

Spring默认将事务的超时时间timeout(单位秒)的值设置为 -1,即没有超时时间

指定回滚

Spring中的@Transactional注解中:

  • 通过rollbackFor来指定回滚的异常类,可以是0个或多个,默认只回滚RuntimeException和Error异常
  • 通过noRollbackFor来指定不回滚的异常类
0条评论
头像
ICP证 : 浙ICP备18021271号