Mybatis源码笔记之浅析StatementHandler

时间:2022-07-24
本文章向大家介绍Mybatis源码笔记之浅析StatementHandler,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

概述

职责

首先了解一下statementHandler职责:主要负责处理MyBatis与JDBC之间Statement的交互,通俗而言就是负责操作Statement对象与数据库之间的交互。其执行过程中主要依赖ParameterHandler和ResultSetHandler进行参数绑定和结果实体类绑定。

类图

  • BaseStatementHandler:StatementHandler接口的抽象实现类,主要用于简化StatementHandler接口的实现难度,适配 器设计模式的体现,主要有三个实现类:
  1. SimpleStatementHandler:管理Statement对象并向数据库推送不需要预编译的SQL语句;
  2. PreparedStatementHandler:管理Statement对象并向数据库推送需要预编译的SQL语句;
  3. CallableStatementHandler:管理Statement对象并调用数据库中的存储过程;
  • RoutingStatementHandler:StatementHandler接口的另一个实现类,并没有对Statement对象其实际执行作用,只是根据StatementType来创建一个代理,代理的主要对象即对应BaseStatementHandler的三种实现类。 从源码层面理解RoutingStatementHandler:
 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }

源码

下面主要以查询为例,debugger源码层面分析。SQL执行开始皆从SqlSession开始。

  • DefaultSqlSession 该类中针对增删改查存在多个重载方法,以selectList为例;
@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      //调用Executor中的query
      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();
    }
  }
  • BaseExecutor
 /**
     * 查询方法,专门提供select执行的方法
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //获取查询SQL
        BoundSql boundSql = ms.getBoundSql(parameter);
        //创建缓存的key,即作为HashMap中的key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //执行查询
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

此段代码块中涉及到BoundSQL对象,此处简单提一下相关概念,后期有时间专门总结一下BoundSQl. BoundSQl对象主要是用于存储SQL语句,以及对应的参数相关对象。 继续调用BaseExcutor中的重载query方法:

 /**
     * 执行查询逻辑,
     * 首先从缓存中获取数据,缓存中有数据则进行处理存储过程;
     * 如果缓存中没有数据,则交互数据库查询数据,则将查询结果添加到缓存中
     */
    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        //如果不是嵌套查询,且动态查询语句中flushCache = true时即<select id= "xx" flushCache = true>才会清空缓存
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            //嵌套查询层数+1
            queryStack++;
            //首先从一级缓存中进行查询:根据key获取对象
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                //从缓存中获取对象,则进行处理存储过程
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                //否则即缓存中没有对应的数据,则交互数据库从数据库中查询数据
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }
        if (queryStack == 0) {
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }

继续跟进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;
    }

继续跟进doQuery方法,发现BaseExecutor抽象类中该方法并没有实体,仅提供一个钩子方法,而是交给其子类实现,这里体现了模板设计模式。

 /**交互数据库,查询数据*/
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
            throws SQLException;

StatementHandler对象创建

  • SimpleExcutor 从BaseExcutor类中跟进其子类SimpleExecutor看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();
      //Configuration中获取StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

到目前为止,我们发现了StatementHandler对象的来源自Configuration中newStatementHandler方法创建; 惊喜若现,继续跟进去;

  • configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建StatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

最终,我们可以明确地发现StatementHandler对象是由其子类RoutingStatementHandler创建的,那么它创建的具体逻辑又是如何的呢?真相即将浮出水面,我们跟进RoutingStatementHandler的构造函数;

  • RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

看到一段很熟悉的代码,我们在文章开头已经将其展现出来了。 根据statementType的类型来判断是哪一种StatementHandler的实现,并且RoutingStatementHandler维护了一个delegate对象,通过delegate对象来实现对实际Handler对象的调用。这里涉及到了一个对象MappedStatement。

  • SimpleExecutor 众所周知,Excutor是主要负责执行对数据库的操作主要执行者,经历上面分析StatementHandler对象的创建过程,下面继续回归到SimpleExectuor中; 获取到StatementHandler之后,首先进入prepareStatement方法,该方法就是为了获取Statement对象,并设置Statement对象中的参数:
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;
  }
  • BaseStatementHandler prepare方法负责生成Statement实例对象
@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
  • prepareStatementHandler parameterize方法用于处理Statement实例对应的参数。此处我们跟进下去,便可以了解ParameterHandler是如何解析参数的过程。
@Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
  • ParameterHandler 此接口只有一个默认实现类DefaultParameterHandler,跟进setParameters方法
@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // parameterMappings 就是对 #{} 或者 ${} 里面参数的封装
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      // 如果是参数化的SQL,便需要循环取出并设置参数的值
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 如果参数类型不是 OUT ,这个类型与 CallableStatementHandler 有关
        // 因为存储过程不存在输出参数,所以参数不是输出参数的时候,就需要设置。
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          // 得到#{}  中的属性名
          String propertyName = parameterMapping.getProperty();
          // 如果 propertyName 是 Map 中的key
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            // 通过key 来得到 additionalParameter 中的value值
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            // 如果不是 additionalParameters 中的key,而且传入参数是 null, 则value 就是null
            value = null;
          }
          // 如果 typeHandlerRegistry 中已经注册了这个参数的 Class对象,即它是Primitive 或者是String 的话
          else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            // 否则就是 Map
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 在通过SqlSource 的parse 方法得到parameterMappings 的具体实现中,我们会得到parameterMappings的typeHandler
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          // 获取typeHandler 的jdbc type
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
  • SampleExecutor 执行完成参数的解析,继续回归到SampleExecutor#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();
      //Configuration中获取StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

后面再次调用query方法,即SampleStatementHandler,PrepareStatementHandler,CallableStatementHandler中方法执行结果实体类绑定,这个具体过程解析,后面计划专门有文章总结。

总结

经历上面的跟踪源码,我们可以了解到StatementHandler对象具体的创建过程,以及参数和结果绑定的流程。

新手跟踪源码,若存在错误或者不足之处,希望大佬及时指正!最后,希望大家多多支持,转发,点赞,关注,谢谢。