美文网首页
mybatis SqlSession执行Mapper过程

mybatis SqlSession执行Mapper过程

作者: dylan丶QAQ | 来源:发表于2020-08-31 20:59 被阅读0次

起因:在工作中常常要用到mybatis框架,如果对其执行流程不清楚的话,就会有一种出了bug不知道要去什么地方找的尴尬。本文为学习《Mybatis源码深度解析》后的总结。感谢江荣波的这本书。


MyBatis通过动态代理将Mapper方法的调用转换为调用SqlSession提供的增删改查方法,以Mapper的Id作为参数,执行数据库的增删改查操作

以SELECT语句为例介绍SqlSession执行Mapper的过程。SqlSession接口只有一个默认的实现,即DefaultSqlSession。DefaultSqlSession类对SqlSession接口中定义的selectList()方法的实现:

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

在DefaultSqlSession的selectList()方法中,首先根据Mapper的Id从Configuration对象中获取对应的MappedStatement对象,然后以MappedStatement对象作为参数,调用Executor实例的query()方法完成查询操作。

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

在BaseExecutor类的query()方法中,首先从MappedStatement对象中获取BoundSql对象,BoundSql类中封装了经过解析后的SQL语句及参数映射信息。然后创建CacheKey对象,该对象用于缓存的Key值。接着调用重载的query()方法。

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

在重载的query()方法中,首先从MyBatis一级缓存中获取查询结果,如果缓存中没有,则调用BaseExecutor类的queryFromDatabase()方法从数据库中查询。

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
           ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

在queryFromDatabase()方法中,调用doQuery()方法进行查询,然后将查询结果进行缓存,doQuery()是一个模板方法,由BaseExecutor子类实现。在学习MyBatis核心组件时,我们了解到Executor有几个不同的实现,分别为BatchExecutor、SimpleExecutor和ReuseExecutor。


doQuery

在SimpleExecutor类的doQuery()方法中,首先调用Configuration对象的newStatementHandler()方法创建StatementHandler对象。newStatementHandler()方法返回的是RoutingStatementHandler的实例。在RoutingStatementHandler类中,会根据配置Mapper时statementType属性指定的StatementHandler类型创建对应的StatementHandler实例进行处理,例如statementType属性值为SIMPLE时,则创建SimpleStatementHandler实例。

  @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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

StatementHandler对象创建完毕后,接着调用SimpleExecutor类的prepareStatement()方法创建JDBC中的Statement对象,然后为Statement对象设置参数操作。Statement对象初始化工作完成后,再调用StatementHandler的query()方法执行查询操作。

SimpleExecutor类中prepareStatement()方法的具体内容

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

在SimpleExecutor类的prepareStatement()方法中,首先获取JDBC中的Connection对象,然后调用StatementHandler对象的prepare()方法创建Statement对象,接着调用StatementHandler对象的parameterize()方法(parameterize()方法中会使用ParameterHandler为Statement对象设置参数)。具体逻辑读者可以参考MyBatis对应的源代码。MyBatis的StatementHandler接口有几个不同的实现类,分别为SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。MyBatis默认情况下会使用PreparedStatementHandler与数据库交互。接下来我们了解一下PreparedStatementHandler的query()方法的实现,

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

在PreparedStatementHandler的query()方法中,首先调用PreparedStatement对象的execute()方法执行SQL语句,然后调用ResultSetHandler的handleResultSets()方法处理结果集。ResultSetHandler只有一个默认的实现,即DefaultResultSetHandler类,DefaultResultSetHandler处理结果集的逻辑在第4章介绍MyBatis核心组件时已经介绍过了。

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

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

    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);
      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类的handleResultSets()方法具体逻辑如下:
(1)首先从Statement对象中获取ResultSet对象,然后将ResultSet包装为ResultSetWrapper对象,通过ResultSetWrapper对象能够更方便地获取数据库字段名称以及字段对应的TypeHandler信息。
(2)获取Mapper SQL配置中通过resultMap属性指定的ResultMap信息,一条SQL Mapper配置一般只对应一个ResultMap。
(3)调用handleResultSet()方法对ResultSetWrapper对象进行处理,将结果集转换为Java实体对象,然后将生成的实体对象存放在multipleResults列表中。
(4)调用collapseSingleResultList()方法对multipleResults进行处理,如果只有一个结果集,就返回结果集中的元素,否则返回多个结果集。

MyBatis如何通过调用Mapper接口定义的方法执行注解或者XML文件中配置的SQL语句这一整条链路介绍完毕。


不要以为每天把功能完成了就行了,这种思想是要不得的,互勉~!

相关文章

网友评论

      本文标题:mybatis SqlSession执行Mapper过程

      本文链接:https://www.haomeiwen.com/subject/mjmpsktx.html