java源码学习-Mybatis(3)执行sql过程

前文:Mybatis与数据库建立连接

jdbc执行流程图

在这里插入图片描述
执行sql的过程就是发起请求的过程, 前文中已经学习了如何建立并获取数据库连接, 本文主要就是学习一下在service调用mapper获取结果的过程, 也就是上图的3~6的过程

方法调用链

首先通过上文, 我们知道了Mybatis执行sql必须要先获取数据库的连接, 所以我们在SimpleExecutor这个类中的prepareStatement方法中获取了connection, 我们找到调用prepareStatement的方法, 这个方法应该就是查询方法doQuery

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 这里是获取一个statement的handler, handler主要是处理生成statement的
      // 在newStatementHandler中还过了一次拦截器链
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 使用handler获取一个statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

至于statement是如何生成的, 我们以后再看

点击进入query方法

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  	// ps中的内容截图在下方
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

在这里插入图片描述
我们可以看见, 这里存放的是我们的入参, 以及前面处理好的sql语句, 即实际在mysql中使用的sql语句,
PreparedStatement 的execute方法就是将sql语句送往mysql执行

接下来这个statement通过getResultSet()方法获取结果集, 这里用到了代理
在这里插入图片描述
这是最后执行的方法

   public ResultSet getResultSet() throws SQLException {
   	// 传入的statement中有delegate属性, delegate可以getResultSet
      final ResultSet resultSet = delegate.getResultSet();
      if (resultSet != null) {
         if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).delegate != resultSet) {
            proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet);
         }
      }
      else {
         proxyResultSet = null;
      }
      return proxyResultSet;
   }

这一步的return中会处理上面获得的结果集

  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -1) {
          // no more results. Must be no resultset
          break;
        }
      }
    }
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }

封装结果集

  public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    // 获取元数据
    final ResultSetMetaData metaData = rs.getMetaData();
    // 获取查询出来的字段个数
    final int columnCount = metaData.getColumnCount();
    // 循环设置字段名以及mysql的变量类型和java的类型映射
    for (int i = 1; i <= columnCount; i++) {
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
    }
  }

结果集返回给resultSetHandler
在这里插入图片描述

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // 这里接收上面的结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    // 处理指定个数的结果集
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 进入DefaultResultSetHandler中的结果集处理
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

DefaultResultSetHandler中的结果集处理, 具体操作不在本文描述, 以后再讲
通过这个方法处理结果集, 会返回我们需要的java类型, 并且将字段的值注入
handleRowValues这个方法中会处理字段和值的映射

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      // 对应于jdbc中的关闭结果集操作
      closeResultSet(rsw.getResultSet());
    }
  }

结果集关闭后就层层返回到doQuery关闭statement, 由于我们的结果设置的不是list也不是map, 所以使用的是DefaultSqlSession中的selectOne方法

  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    // 可以看见无论结果设置是否多个, 都是调用selectList去数据库中查询
    List<T> list = this.selectList(statement, parameter);
    // 由于是selectOne, 所以这里返回的list必须是只有一个元素
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      // 这里就是我们经常看见的异常, 单列查询返回多结果
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

最后的结果返回SqlSessionTemplate类, 这个拦截器是template的内部类,
是在MapperMethod类中的execute方法的selectOne进入的方法,
大家去看看Template类可以发现, selectOne返回的是this.sqlSessionProxy.selectOne,
而这个sqlSessionProxy属性是构造器中new出来的内部类SqlSessionInterceptor
也就是最后进入的其实是下面这个invoke方法

  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
      	// 这里这个invoke进入的是DefaultSqlSession中的selectOne方法
      	// 真正被执行的selectOne
        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);
        }
      }
    }
  }

我们调用自己写的mapper中的方法之后, 会通过代理进入MapperProxy中, 可以看见这里调用了上面的execute, 这个invoke返回之后, 就会在我们的service得到最终的java对象

  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 (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

后记

本篇主要是讲了讲Mybatis在执行sql的时候, 得到数据库连接后, 何时创建statement, 何时创建resultSet
不难发现, mybatis的很多实现都是依赖代理, 我们调用mapper方法后就会进入MapperProxy, 这里就是一个代理
如果除去所有代理的话, 其实流程就是先判断返回的结果集个数从而决定调用selectOne还是selectList方法, 然后判断一级缓存, 如果不存在就去数据库查找, 查找过程就是先通过Hikari中获取的connection创建statement, 然后提交sql到数据库, 获得结果后通过resultSetHandler处理结果返回到我们调用mapper方法的service中

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值