【spring源码探索】申明式事务是如何保证多个DAO用的同一个connection

前言

突然好奇Spring的申明式事务是怎么实现的,肯定是后置处理增强的就不用说了。从我多年写代码的经验来说,肯定是生成一个代理对象,在调用service方法之前就会创建事务,然后整个service方法执行完后再提交或者异常回滚。但是这就有一个问题,一个service调用多个dao,以DAO层框架mybatis为例,调用的时候咱们并没有给他传任何connection,既然要保证事务,那么每个DAO里面传的connection肯定是同一个,那么框架底层是如何实现的呢?

过程

为了探究一下实现原理,咱们先写一个简单的DEMO

@Override
    @Transactional
    public void test() {
        List<GoodsOwnerDTO> dataA = wmsDao.selectGoodsOwnerByWarehId("1");
        List<GoodsOwnerDTO> dataB = wmsDao.selectGoodsOwnerByWarehId("2");
    }

咱们的目的也很简单,就在第二次调用查询的时候看他从哪取的connection,然后开始DEBUG。

首先是MybatisMapperProxy类里面的

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (method.isDefault()) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
        //进入这个方法
        return mapperMethod.execute(sqlSession, args);
    }

继续跟MybatisMapperMethod

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                	//进入这个方法
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // TODO 这里下面改了
                    if (IPage.class.isAssignableFrom(method.getReturnType()) && args != null
                        && IPage.class.isAssignableFrom(args[0].getClass())) {
                        result = ((IPage<?>) args[0]).setRecords(executeForIPage(sqlSession, args));
                        // TODO 这里上面改了
                    } else {
                        result = sqlSession.selectOne(command.getName(), param);
                        if (method.returnsOptional()
                            && (result == null || !method.getReturnType().equals(result.getClass()))) {
                            result = Optional.ofNullable(result);
                        }
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }

继续跟进到了executeForMany方法

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<E> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            result = sqlSession.selectList(command.getName(), param, rowBounds);
        } else {
        	//进入这个方法
            result = sqlSession.selectList(command.getName(), param);
        }
        // issue #510 Collections & arrays support
        if (!method.getReturnType().isAssignableFrom(result.getClass())) {
            if (method.getReturnType().isArray()) {
                return convertToArray(result);
            } else {
                return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
            }
        }
        return result;
    }

跟进到SqlSessionTemplate的selectList

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.selectList(statement, parameter);
  }

紧接着是SqlSessionTemplate的内部类SqlSessionInterceptor的invoke方法

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //注意看这里,这里就是去拿sqlsession了
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }

到了这一步,总算有点苗头了,因为我们看到sqlSession了,继续跟到了SqlSessionUtils的getSqlSession

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
	
	//进入这个方法
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

很多框架总是喜欢用holder来持有比较重要的对象,咱们看到holder可以稍微关注一下,然后继续跟进入到TransactionSynchronizationManager的getResource方法

public static Object getResource(Object key) {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		//就是这个方法
		Object value = doGetResource(actualKey);
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
					Thread.currentThread().getName() + "]");
		}
		return value;
	}

终于看到doXXX了,不出意外应该是要见底了,继续进doGetResource方法。

@Nullable
	private static Object doGetResource(Object actualKey) {
		//关键地方
		Map<Object, Object> map = resources.get();
		if (map == null) {
			return null;
		}
		Object value = map.get(actualKey);
		// Transparently remove ResourceHolder that was marked as void...
		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
			map.remove(actualKey);
			// Remove entire ThreadLocal if empty...
			if (map.isEmpty()) {
				resources.remove();
			}
			value = null;
		}
		return value;
	}

代码不长,甚至很简单,就是从一个map里面通过KEY取一个值返回就行,重点也就在这。这个map里面存的啥,咱们看下断点追踪的图片
在这里插入图片描述
一个是DATASOURCE,一个是sqlsessionFactory,好,真相大白,然后在看来一下这个resources是个啥,ThreadLocal。

private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

到了这里,相信大家应该都明白了,但是可能某些同学还不知道sqlSession是个啥玩意,那也没关系,咱们再看一下下图
在这里插入图片描述
sqlsession里面的executor里面持有一个transcation对象,然后transcation里面又持有一个connection对象,否管其他东西是啥,咱们就能保证一个线程里面获取到的connection都是同一个了。如果还不懂,建议去看一下ThreadLocal的用法或者原理了。

备注

以上仅个人理解,不保证没有疏漏之处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是一个有理想的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值