• mybatis 源码分析(六)StatementHandler 主体结构分析


    分析到这里的时候,mybatis 初始化、接口、事务、缓存等主要功能都已经讲完了,现在就还剩下 StatementHandler 这个真正干活的家伙没有分析了;所以接下来的博客内容主要和数据库的关系比较密切,而 StatementHandler 的主要流程也基本是和 JDBC 的流程是一一对应的;

    一、StatementHandler 执行流程

    在 mybatis 系列文章的第一篇,我放了一张 mybatis 整体的执行流程图:

    从上面的图中也能比较清楚的看到 StatementHandler 的职责:获取 Statement -> 设置参数 -> 查询数据库 -> 将查询结果映射为 JavaBean,从这里也能看到是和我们使用原生 JDBC 的流程是一样的;而整个过程 StatementHandler 又将其拆分成了部分:

    • KeyGenerator:主键设置
    • ParameterHandler:参数设置
    • ResultSetHandler:结果集设置

    这里我们首先介绍 StatementHandler 的类结构:

    • RoutingStatementHandler:路由处理器,这个相当于一个静态代理,根据 MappedStatement.statementType 创建对应的对处理器;
    • SimpleStatementHandler:不需要预编译的简单处理器;
    • PreparedStatementHandler:预编译的 SQL 处理器;
    • CallableStatementHandler:主要用于存储过程的调度;

    这里的 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler 同时也和 JDBC 的三个 Statement 一一对应;首先我们还是先看一下接口方法:

    public interface StatementHandler {
      Statement prepare(Connection connection) throws SQLException; // 获取 Statement
      void parameterize(Statement statement) throws SQLException;   // 参数化
      void batch(Statement statement) throws SQLException;          // 批处理
      int update(Statement statement) throws SQLException;
      <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
      BoundSql getBoundSql();                 // 获取绑定sql
      ParameterHandler getParameterHandler(); // 得到参数处理器
    }
    

    其中公共的方法都封装到了 BaseStatementHandler 中(这里使用的是模版模式);

    // 获取 Statement
    @Override
    public Statement prepare(Connection connection) throws SQLException {
      ErrorContext.instance().sql(boundSql.getSql());
      Statement statement = null;
      try {
        statement = instantiateStatement(connection); // 实例化 Statement
        setStatementTimeout(statement);               // 设置超时
        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);
      }
    }
    
    // 由子类提供不同的实例化
    protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
    

    二、StatementHandler 子类

    1. RoutingStatementHandler

    静态代理模式,根据 MappedStatement.statementType 创建对应的对处理,默认是 PREPARED,可以使用 XML 配置或者注解的方式指定;

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
      // 创建路由选择语句处理器
      StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
      // 插件拦截
      statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
      return statementHandler;
    }
    
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
      switch (ms.getStatementType()) {  // 根据语句类型,委派到不同的语句处理器(STATEMENT|PREPARED|CALLABLE)
        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());
      }
    }
    

    2. SimpleStatementHandler

    其功能和 JDBC.Statement 对应,从初始化方法也可以看到使用的是简单 Statement;

    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
      // 调用 Connection.createStatement
      if (mappedStatement.getResultSetType() != null) {
        return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
      } else {
        return connection.createStatement();
      }
    }
    
    @Override
    public int update(Statement statement) throws SQLException {
      String sql = boundSql.getSql();
      Object parameterObject = boundSql.getParameterObject();
      KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
      int rows;
      if (keyGenerator instanceof Jdbc3KeyGenerator) {
        statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
        rows = statement.getUpdateCount();
        keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
      } else if (keyGenerator instanceof SelectKeyGenerator) {
        statement.execute(sql);
        rows = statement.getUpdateCount();
        keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
      } else {
        //如果没有keyGenerator,直接调用Statement.execute和Statement.getUpdateCount
        statement.execute(sql);
        rows = statement.getUpdateCount();
      }
      return rows;
    }
    

    这里在更新的时候需要区分 KeyGenerator,因为使用的是简单 Statement,所以需要在查询的时候指定返回主键 statement.execute(sql, Statement.RETURN_GENERATED_KEYS);

    3. PreparedStatementHandler

    其功能和 PreparedStatement 对应,这里初始化的时候可以看到比 SimpleStatementHandler 要复杂一些,因为 PreparedStatement 更新返回主键有三个方法,详细分析后面会单独放一篇详细讲解;

    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
      //调用Connection.prepareStatement
      String sql = boundSql.getSql();
      if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
        String[] keyColumnNames = mappedStatement.getKeyColumns();
        if (keyColumnNames == null) {
          return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
        } else {
          return connection.prepareStatement(sql, keyColumnNames);
        }
      } else if (mappedStatement.getResultSetType() != null) {
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
      } else {
        return connection.prepareStatement(sql);
      }
    }
    

    还有一点不同的就是 PreparedStatement 需要参数化设置,就是设置预编译 SQL 对应占位符的参数;

    // DefaultParameterHandler
    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {
      ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
      List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      if (parameterMappings != null) {
        // 循环设参数
        for (int i = 0; i < parameterMappings.size(); i++) {
          ParameterMapping parameterMapping = parameterMappings.get(i);
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            // 如果不是OUT,才设进去
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
              // 若有额外的参数, 设为额外的参数
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
              // 若参数为null,直接设null
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
              // 若参数有相应的TypeHandler,直接设object
              value = parameterObject;
            } else {
              // 除此以外,MetaObject.getValue反射取得值设进去
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              value = metaObject.getValue(propertyName);
            }
            TypeHandler typeHandler = parameterMapping.getTypeHandler();
            JdbcType jdbcType = parameterMapping.getJdbcType();
            if (value == null && jdbcType == null) {
              // 不同类型的set方法不同,所以委派给子类的setParameter方法
              jdbcType = configuration.getJdbcTypeForNull();
            }
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          }
        }
      }
    }
    

    另外还有 CallableStatementHandler ,代码也很简单,这里就不详细分析了;

  • 相关阅读:
    ms4w php配置xdebug
    转载: js 调用父窗口函数-iframe父窗口和子窗口相互的调用方法
    禁止apache列出站内目录
    块元素和行内元素之间的转换,overflow与visibility
    float浮动定位
    绝对定位和固定定位
    相对定位
    边框样式的设置
    div盒子模型
    CSS修饰表格
  • 原文地址:https://www.cnblogs.com/sanzao/p/11435314.html
Copyright © 2020-2023  润新知