美文网首页一些收藏
MyBatis原理(三)——SQL处理器StatementHan

MyBatis原理(三)——SQL处理器StatementHan

作者: Lnstark | 来源:发表于2021-12-04 23:53 被阅读0次

    一、作用

    StatementHandler作用主要是statement的创建,预编译、设置参数、SQL的执行以及结果的处理。它存在于执行器里,每次执行query或update时都会创建一个StatementHandler。

    public interface StatementHandler {
    
      // 根据连接获取statement
      Statement prepare(Connection connection, Integer transactionTimeout)
          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;
      // 查询游标
      <E> Cursor<E> queryCursor(Statement statement)
          throws SQLException;
      // 获取动态SQL
      BoundSql getBoundSql();
      // 获取结果处理器
      ParameterHandler getParameterHandler();
    
    }
    

    二、实现类

    StatementHandler类图.png

    和执行器类似的,它也有一个抽象类BaseStatementHandler实现,然后三个具体的实现类继承BaseStatementHandler:

    • SimpleStatementHandler:用于Statement的创建和处理
    • PreparedStatementHandler:用于需要预编译的PreparedStatement的创建和处理,基本上都是用的这个,毕竟预编译防注入
    • CallableStatementHandler:用于存储过程CallableStatement的创建和处理

    BaseStatementHandler里有ParameterHandler和ResultSetHandler,分别用于参数的处理和结果的处理。
    RoutingStatementHandler下面会说到。

    三、StatementHandler的处理流程

    整体流程如下图:


    StatementHandler处理流程.png

    代码的话我们可以看SimpleExecutor的doQuery方法:

    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
      Statement stmt = null;
      try {
        // 根据MappedStatement获取Configuration
        Configuration configuration = ms.getConfiguration();
        // 创建StatementHandler
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 创建statement,设置参数
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 查询并处理结果
        return handler.query(stmt, resultHandler);
      } finally {
        closeStatement(stmt);
      }
    }
    

    3.1 StatementHandler的创建

    我们看Configuration的newStatementHandler方法:

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
      // 创建RoutingStatementHandler
      StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
      // 加入到拦截器链
      statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
      return statementHandler;
    }
    

    再看RoutingStatementHandler:

    public class RoutingStatementHandler implements StatementHandler {
    
      private final StatementHandler delegate;
    
      public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 根据MappedStatement的类型来创建不同的StatementHandler
        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());
        }
      
      }
      // ...
    }
    

    就一个装饰器,其实就起到一个工厂的作用,具体方法全都是由delegate来直接执行,没啥好说的。
    statementType是在mapper的方法上设置的,例:

    @Options(statementType = StatementType.STATEMENT)
    List<CustomerVo> selectCustomers(@Param("roleId") Integer roleId);
    

    statementType 默认为StatementType.PREPARED。

    3.2 创建statement

    SimpleExecutor的prepareStatement方法:

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
      Statement stmt;
      // 获取连接,在BaseExecutor里通过Transaction来获取
      Connection connection = getConnection(statementLog);
      // 创建statement
      stmt = handler.prepare(connection, transaction.getTimeout());
      // 设置参数
      handler.parameterize(stmt);
      return stmt;
    }
    

    我们直接看BaseStatementHandler的prepare方法:

    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
      // 省略部分代码...
      
      // 子类里实例化statement
      statement = instantiateStatement(connection);
      // 设置超时时间,可在配置文件里配
      setStatementTimeout(statement, transactionTimeout);
      // 设置返回的大小
      setFetchSize(statement);
      return statement;
      
      // 省略部分代码...
    }
    

    子类的instantiateStatement实现会创建对应类型的Statement。

    3.3 参数设置

    我们看MapperProxy的invoke方法,这是动态代理的后的增强方法,参数args就是我们传给mapper的参数。

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      try {
        // 一般是false
        if (Object.class.equals(method.getDeclaringClass())) {
          return method.invoke(this, args);
        } else {
          // 创建MapperMethodInvoker并执行
          return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    

    cachedInvoker方法:

    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
      try {
        // 从map缓存中获取MapperMethodInvoker,如果没有的话创建一个
        return MapUtil.computeIfAbsent(methodCache, method, m -> {
          // 是否default方法,一般false
          if (m.isDefault()) {
            try {
              if (privateLookupInMethod == null) {
                return new DefaultMethodInvoker(getMethodHandleJava8(method));
              } else {
                return new DefaultMethodInvoker(getMethodHandleJava9(method));
              }
            } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                | NoSuchMethodException e) {
              throw new RuntimeException(e);
            }
          } else {
            // 创建PlainMethodInvoker,里面是一个MapperMethod
            
            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
          }
        });
      } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
      }
    }
    

    虽然返回了PlainMethodInvoker,但他的invoke方法只是单纯的调用了MapperMethod的execute方法,我们直接看MapperMethod的execute方法:

    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;
      // 根据MapperMethod的类型(增删改查)来执行不通的操作
      // 其实都是先进行method.convertArgsToSqlCommandParam转换参数
      // 再执行sqlSession相应的操作
      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:
          // 查询的话会根据返回的结果类型,采用不同的结果处理方式
          // 但是参数的处理都还是先经过method.convertArgsToSqlCommandParam
          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);
            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;
    }
    

    这里的method是MethodSignature,在创建MapperMethod时创建的:

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
      this.command = new SqlCommand(config, mapperInterface, method);
      this.method = new MethodSignature(config, mapperInterface, method);
    }
    

    我们继续看MethodSignature的convertArgsToSqlCommandParam方法:

    public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }
    

    他里面有一个ParamNameResolver用于参数名的处理。在构造ParamNameResolver时会进行参数名的解析:

    public ParamNameResolver(Configuration config, Method method) {
      // 配置项,是否用方法的参数名
      this.useActualParamName = config.isUseActualParamName();
      final Class<?>[] paramTypes = method.getParameterTypes();
      // 获取所有参数的注解
      final Annotation[][] paramAnnotations = method.getParameterAnnotations();
      final SortedMap<Integer, String> map = new TreeMap<>();
      int paramCount = paramAnnotations.length;
      // get names from @Param annotations
      // 遍历所有参数的注解
      for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // 跳过特殊类型,RowBounds及其子类或者ResultHandler实现类
        if (isSpecialParameter(paramTypes[paramIndex])) {
          // skip special parameters
          continue;
        }
        String name = null;
        // 如果存在@Param注解,就用它的value作为name
        for (Annotation annotation : paramAnnotations[paramIndex]) {
          if (annotation instanceof Param) {
            hasParamAnnotation = true;
            name = ((Param) annotation).value();
            break;
          }
        }
        // 如果没有@Param注解,那就用反射获取参数名
        // jdk8开启编译参数-parameters,编译才会保留参数名,否则的话会被转换成args0, args1...
        // 如果反射也拿不到参数名的话,那就直接用下标"0", "1", ...
        if (name == null) {
          // @Param was not specified.
          if (useActualParamName) {
            name = getActualParamName(method, paramIndex);
          }
          if (name == null) {
            // use the parameter index as the name ("0", "1", ...)
            // gcode issue #71
            name = String.valueOf(map.size());
          }
        }
        // 将参数下标,参数名放到一个map里,然后返回
        map.put(paramIndex, name);
      }
      names = Collections.unmodifiableSortedMap(map);
    }
    

    getNamedParams方法:

    public Object getNamedParams(Object[] args) {
      // names就是上面解析完的 参数下标 => 参数名 map
      final int paramCount = names.size();
      // 如果没有参数,那么返回null
      if (args == null || paramCount == 0) {
        return null;
      } else if (!hasParamAnnotation && paramCount == 1) {
        // 如果参数只有一个并且没有@Param注解,那么就直接用对象
        Object value = args[names.firstKey()];
        // 如果是集合的话,就把它丢到一个map里再返回
        return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
      } else {
        // 如果是多个参数,那就从names里取出参数名,构造出 参数名 => 参数值的map
        // 另外还用param1, param2的形式把参数也塞到map里
        // 例如:传了两个参数 Integer id 1, String name 张三, 
        // 那生成的map就可能是 id => 1, name => 张三, param1 => 1, param2 => 张三
        final Map<String, Object> param = new ParamMap<>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
          param.put(entry.getValue(), args[entry.getKey()]);
          // add generic param names (param1, param2, ...)
          final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
          // ensure not to overwrite parameter named with @Param
          if (!names.containsValue(genericParamName)) {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }
    

    转换完生成的这个参数对象,会传给SqlSession相应的方法,上面的MapperMethod的execute方法里。之后SqlSession执行对应的增删改查的方法时,将这个参数对象又传给执行器,执行器在doQuery或doUpdate时,创建StatementHandler,将参数对象传进去。StatementHandler在构造时,会创建ParameterHandler,因此参数对象最终是放在它那。

    我们继续PrepareStatementHandler的parameterize方法:

    public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
    }
    

    ParameterHandler只有一个实现类DefaultParameterHandler,看setParameters的实现:

    public void setParameters(PreparedStatement ps) {
      ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
      // 拿到SQL语句里需要设置的参数列表
      List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      if (parameterMappings != null) {
        // 遍历
        for (int i = 0; i < parameterMappings.size(); i++) {
          ParameterMapping parameterMapping = parameterMappings.get(i);
          // 如果不是出参,一般都是true
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
              // 如果设置了TypeHandler,那就直接用参数对象
              value = parameterObject;
            } else {
              // 将参数对象包装成MetaObject,便于取值
              // MetaObject里面有取值和赋值的操作封装,对不同的对象类型(Map或者普通对象)都能进行取值和赋值
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              // 取出参数对象里的参数值
              value = metaObject.getValue(propertyName);
            }
            TypeHandler typeHandler = parameterMapping.getTypeHandler();
            JdbcType jdbcType = parameterMapping.getJdbcType();
            if (value == null && jdbcType == null) {
              jdbcType = configuration.getJdbcTypeForNull();
            }
            try {
              // 设置参数值
              typeHandler.setParameter(ps, i + 1, value, jdbcType);
            } catch (TypeException | SQLException e) {
              throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
            }
          }
        }
      }
    }
    

    setParameter就看BaseTypeHandler里的,它里面有一行setNonNullParameter(ps, i, parameter, jdbcType);这个是子类里实现的,不同的参数类型有不同的实现,里面就是调用jdbc的PreparedStatement的设置参数的方法。例如StringTypeHandler:

    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
          throws SQLException {
        ps.setString(i, parameter);
    }
    

    参数的设置是在Mapper里的SQL语句解析完之后进行的,关于SQL语句的解析后面再讲,这里先总结一下参数的设置流程:

    1. 参数转换:在ParamNameResolver里进行,拿到参数名和参数值,转化成
      “参数名 => 参数值”的map
    2. 参数映射:ParameterHandler里进行,取出SQL里的参数名propertyName,用它在上一步生成的map里取出对应的参数值
    3. 参数赋值:TypeHandler里进行,为PrepareStatement设置值。

    参考资料

    B站——MyBatis源码解析大合集
    源码阅读网

    相关文章

      网友评论

        本文标题:MyBatis原理(三)——SQL处理器StatementHan

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