欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识
原文出处: 光闪

初步

本日小伴侣X在开拓进程中碰着了一个bug,并给mybatis提了一个ISSUE:throw ReflectionException when using #{array.length}

大抵说明下该问题,在mapper.xml中,利用#{array.length}来获取数组的长度时,会报出ReflectionException。 代码:

public List<QuestionnaireSent> selectByIds(Integer[] ids) { 
    return commonSession.selectList("QuestionnaireSentMapper.selectByIds", ImmutableMap.of("ids", ids)); 
}

对应的xml:

<select id="selectByIds">
	SELECT * FROM t_questionnaire
	<if test="ids.length > 0">
		WHERE id in
		<foreach collection="ids" open="(" separator="," close=")" item="id">#{id}
		</foreach>
	</if>
	LIMIT #{ids.length}
</select>

下面团结源码对该问题举办阐明

阐明

xml中有两处利用了length,那么这个报错毕竟是哪个引起的呢?

实验把test条件去掉,limit保存后,依然报错。那么可定位出报错是#{ids.length}导致的。

由此引出了两个问题:

  1. XML标签中条件是如何理会的(扩展,foreach是如何理会的数组和荟萃)
  2. #{ids.length}是如何理会的

带着这两个问题,我们进入源码

第一部门 XML标签的理会

在类org.apache.ibatis.scripting.xmltags.XMLScriptBuilder中

private void initNodeHandlerMap() {
    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());
}
protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<SqlNode>();
  NodeList children = node.getNode().getChildNodes();
  for (int i = 0; i < children.getLength(); i++) {
    XNode child = node.newXNode(children.item(i));
    if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
      String data = child.getStringBody("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } 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.");
      }
      handler.handleNode(child, contents);
      isDynamic = true;
    }
  }
  return new MixedSqlNode(contents);
}

在每个对应的Handler中,有相应的处理惩罚逻辑。

以IfHandler为例:

private class IfHandler implements NodeHandler {
  public IfHandler() {
    // Prevent Synthetic Access
  }

  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    String test = nodeToHandle.getStringAttribute("test");
    IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
    targetContents.add(ifSqlNode);
  }
}

在这里主要生成了IfSqlNode,理会在相应的类中

public class IfSqlNode implements SqlNode {
  private final ExpressionEvaluator evaluator;
  private final String test;
  private final SqlNode contents;

  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  @Override
  public boolean apply(DynamicContext context) {
    // OGNL执行test语句
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }
}

ExpressionEvaluator利用的是OGNL表达式来运算的。

再举一个高级的例子:ForEachSqlNode,个中包罗对数组和Collection以及Map的理会,焦点是通过OGNL获取对应的迭代器:

final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);

public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
  Object value = OgnlCache.getValue(expression, parameterObject);
  if (value == null) {
    throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
  }
  if (value instanceof Iterable) {
    return (Iterable<?>) value;
  }
  if (value.getClass().isArray()) {
      // the array may be primitive, so Arrays.asList() may throw
      // a ClassCastException (issue 209).  Do the work manually
      // Curse primitives! :) (JGB)
      int size = Array.getLength(value);
      List<Object> answer = new ArrayList<Object>();
      // 数组为何要这样处理惩罚?参考跋文1
      for (int i = 0; i < size; i++) {
          Object o = Array.get(value, i);
          answer.add(o);
      }
      return answer;
  }
  if (value instanceof Map) {
    return ((Map) value).entrySet();
  }
  throw new BuilderException("Error evaluating expression '" + expression + "'.  Return value (" + value + ") was not iterable.");
}

中间有个有意思的注释,参考跋文1.

第二部门 ${},#{}的理会

首先需要明晰:

  1. ${}: 利用OGNL动态执行内容,功效拼在SQL中
  2. #{}: 作为参数标志符理会,把理会内容作为prepareStatement的参数。

对付xml标签,个中的表达式也是利用的${}的理会方法,利用OGNL表达式来理会。

对付参数标志符理会,mybatis利用的是本身设计的理会器,利用反射机制获取各类属性。

以#{bean.property}为例,利用反射取到bean的属性property值。他的理会进程如下:

  1. BaseExecutor.createCacheKey要领