mybatis第八话 - mybaits之ParameterHandler参数处理源码分析

677 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本章主要记录了mybatis参数处理源码分析

1.前因

mybatis源码也分析过了,插件也分析完了,但是在分析插件的过程中产生一个疑问? 找到源码MySqlDialect

@Override
public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, Page page, BoundSql boundSql, CacheKey pageKey) {
 //参数列表
    paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
    paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
    //处理pageKey
    pageKey.update(page.getStartRow());
    pageKey.update(page.getPageSize());
    //处理参数配置
    if (boundSql.getParameterMappings() != null) {
        List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>(boundSql.getParameterMappings());
        if (page.getStartRow() == 0) {
         //在原有的mappinglist中添加key名
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, int.class).build());
        } else {
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_FIRST, long.class).build());
            newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, int.class).build());
        }
        MetaObject metaObject = MetaObjectUtil.forObject(boundSql);
        //更新
        metaObject.setValue("parameterMappings", newParameterMappings);
    }
    return paramMap;
}

为什么在参数中添加了两个值,在parameterMappings只添加了一个也可以执行成功,这中间是怎么实现的呢?

2.源码分析

2.1 MappedStatement#getBoundSql

//从这里开始 CachingExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //处理sql和参数
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

//MappedStatement#getBoundSql 
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

sqlSource是个接口有四个实现类,要看走哪个实现类,还得再回顾一下xml解析流程

2.2 SqlSource实现类分析流程

  • 回顾一下sql xml文件的解析过程 MybatisAutoConfiguration入口类

初始bean sqlSessionFactory调用getObject() -> SqlSessionFactoryBean#buildSqlSessionFactory()-> XMLMapperBuilder#parse ->configurationElement ->buildStatementFromContext XMLStatementBuilder#parseStatementNode(sqlSource在此初始化) -> addMappedStatement 生成一个MappedStatement 即前面调用的ms对象

  • 直接找到XMLStatementBuilder#parseStatementNode
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

//这里是xml解析 最后到XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  //取决于parseDynamicTags ##
  if (isDynamic) {
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

//parseDynamicTags 省略部分代码
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
  String data = child.getStringBody("");
  TextSqlNode textSqlNode = new TextSqlNode(data);
  //这个为真 isDynamic赋值为true
  if (textSqlNode.isDynamic()) {
    contents.add(textSqlNode);
    isDynamic = true;
  } else {
    contents.add(new StaticTextSqlNode(data));
  }
}
  • TextSqlNode#isDynamic
public boolean isDynamic() {
  DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
  //创建一个解析器 传入参数为DynamicCheckerTokenParser
  GenericTokenParser parser = createParser(checker);
  //解析 ###
  parser.parse(text);
  return checker.isDynamic();
}

private GenericTokenParser createParser(TokenHandler handler) {
  //解析${xxx}参数
  return new GenericTokenParser("${", "}", handler);
}

2.3 ${xxx}参数解析

  • GenericTokenParser
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

//后面有详细分析 这里只贴这几行
public String parse(String text) {
 //根据下标将符号包裹的字段取出来
 expression.append(src, offset, end - offset - 1).append(closeToken);
 //在这里处理 ###
 builder.append(handler.handleToken(expression.toString()));
}
  • 前面传入的handler是DynamicCheckerTokenParser
//就一个修改为true的操作
@Override
public String handleToken(String content) {
  this.isDynamic = true;
  return null;
}
  • 最后在看下XMLScriptBuilder#parseDynamicTags中的else
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());

else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
  String nodeName = child.getNode().getNodeName();
  NodeHandler handler = nodeHandlerMap.get(nodeName);
  if (handler == null) {
    throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
  }
  //从上述的map来分析 只有有动态的关键字 都会走到这里面来
  handler.handleNode(child, contents);
  //那就返回的是true
  isDynamic = true;
}

到这里我们我们基本可以判断出了,只要需要动态的已经就是这个sqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);

  • DynamicSqlSource#getBoundSql
@Override
public BoundSql getBoundSql(Object parameterObject) {
  //构造方法 
  DynamicContext context = new DynamicContext(configuration, parameterObject);
  //这里应该就是 MixedSqlNode ${}符号的解析 ###
  rootSqlNode.apply(context);
  SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
  //#{} 的解析 ###
  SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  context.getBindings().forEach(boundSql::setAdditionalParameter);
  return boundSql;
}

//DynamicContext
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";

public DynamicContext(Configuration configuration, Object parameterObject) {
  if (parameterObject != null && !(parameterObject instanceof Map)) {
    MetaObject metaObject = configuration.newMetaObject(parameterObject);
    boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
    bindings = new ContextMap(metaObject, existsTypeHandler);
  } else {
    bindings = new ContextMap(null, false);
  }
  //将参数保存到这个map中 key 为_parameter
  bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
  bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
  • MixedSqlNode#apply
@Override
public boolean apply(DynamicContext context) {
  //从前面源码得知到TextSqlNode#apply
  contents.forEach(node -> node.apply(context));
  return true;
}

@Override
public boolean apply(DynamicContext context) {
  //这个handler是BindingTokenParser  解析器传值${}
  GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
  //解析 ###
  context.appendSql(parser.parse(text));
  return true; 
}

private GenericTokenParser createParser(TokenHandler handler) {
  return new GenericTokenParser("${", "}", handler);
}

根据字符下标取出对应的参数值的代码就不贴了,直接到

//对应符号包含的参数key
builder.append(handler.handleToken(expression.toString()));
  • BindingTokenParser#handleToken 该类是TextSqlNode的内部类
@Override
public String handleToken(String content) { //content传进来的是参数key
  Object parameter = context.getBindings().get("_parameter");
  if (parameter == null) {
    context.getBindings().put("value", null);
  } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
    context.getBindings().put("value", parameter);
  }
  //取值
  Object value = OgnlCache.getValue(content, context.getBindings());
  String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
  checkInjection(srtValue);
  //直接返回
  return srtValue;
}

这也解释了${}符号的参数从日志打印中来看是直接替换了的,并不是使用的占位符

2.4 #{xxx}参数解析

  • 接上面源码继续分析SqlSourceBuilder#parse
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  //注意这里的handler
  GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  String sql;
  if (configuration.isShrinkWhitespacesInSql()) {
    sql = parser.parse(removeExtraWhitespaces(originalSql));
  } else {
    sql = parser.parse(originalSql);
  }
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
  • parse代码就不贴了,直接走handler的处理吧SqlSourceBuilder#ParameterMappingTokenHandler#handleToken
public String handleToken(String content) {
  //添加到参数数组 && 处理数据类型 
  parameterMappings.add(buildParameterMapping(content));
  //用 ? 占位
  return "?";
}

2.5 sql赋值

到这里为止,使用${}符号的参数已经直接替换进去了,但是#{}的参数还是?占位的,接下来分析下什么时候赋值的

  • SimpleExecutor#doQuery
stmt = prepareStatement(handler, ms.getStatementLog());

//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;
 }

//PreparedStatementHandler#parameterize
@Override
public void parameterize(Statement statement) throws SQLException {
  //可被插件代理 分析直接走默认实现了
  parameterHandler.setParameters((PreparedStatement) statement);
}
  • DefaultParameterHandler#setParameters
public void setParameters(PreparedStatement ps) {
   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);
       ParameterMapping parameterMapping = parameterMappings.get(i);
       if (parameterMapping.getMode() != ParameterMode.OUT) {
         Object value;
       //······ 省略部分代码
         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);
         }
       }
     }
   }
 }

2.6 TypeAliasRegistry映射类型

private final Map<String, Class<?>> typeAliases = new HashMap<>();

  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

//resolveAlias
value = (Class<T>) typeAliases.get(key);

好了,整个分析的过程应该是完结了。 以上就是本章的全部内容了。

上一篇:mybatis第七话 - mybatis插件篇之pagehelper的源码分析 下一篇:mybatis第九话 - 手写实现一个简单的mybatis版本

勿以恶小而为之,勿以善小而不为