Mybatis ステッチング SQL エラーとソースコード分析

I.はじめに

        このプロジェクトでは、入力パラメータがない場合に追加の条件を結合するクエリ SQL を作成しましたが、しばらく読んでも何が間違っているのかわかりませんでした。別名 mybatis が設定されているかどうか、驚くべき推測が浮かび上がりました。 foreach に設定されました。

二、調査

        元のコードは次のとおりです

select
        <include refid="Base_Column_List"/>
        from t_aac_shop_exempt_apply
        where 1 = 1
        <if test="exemptNoList != null and exemptNoList.size > 0">
            and exempt_no in
            <foreach collection="exemptNoList" item="exemptNo" open="(" close=")" separator=",">
                #{exemptNo}
            </foreach>
        </if>
        <if test="exemptNo != null and exemptNo!=''">
            and exempt_no = #{exemptNo}
        </if>
        <if test="applyUserCode != null and applyUserCode!=''">
            and apply_user_code = #{applyUserCode}
        </if>
        <if test="agentNo != null and agentNo!=''">
            and agent_no = #{agentNo}
        </if>
        <if test="serviceAgentNoList != null and serviceAgentNoList.size > 0">
            and agent_no in
            <foreach collection="serviceAgentNoList" item="agentNo" open="(" close=")" separator=",">
                #{agentNo}
            </foreach>
        </if>
        <if test="compensateNo != null and compensateNo!=''">
            and compensate_no = #{compensateNo}
        </if>
        <if test="month != null">
            and month = #{month}
        </if>
        <if test="status != null">
            and status = #{status}
        </if>
        <if test="statusList != null and statusList.size > 0">
            and status in
            <foreach collection="statusList" item="status" open="(" close=")" separator=",">
                #{status}
            </foreach>
        </if>
        <if test="startTime != null">
            and gmt_create <![CDATA[   >=  ]]> #{startTime}
        </if>
        <if test="endTime != null">
            and  #{endTime} <![CDATA[   >=  ]]> gmt_create
        </if>
        and is_deleted = 0

        入力パラメータは

{
    "model":
    {
         "exemptNoList":["MPSQ11739550014177280","MPSQ11750861482491904"],
         "agentNo":"","compensateNo":"","applyUserCode":"",
         "startTime":"2022-04-23 00:00:00",
         "endTime":"2022-07-21 23:59:59"
    },
    "pageIndex":1,"pageSize":10,"queryCount":true,"start":0,"startPos":0
}

        結合したSQL結果はこんな感じ

SELECT
	id,
	exempt_no,
	instance_code,
	agent_no,
	agent_name,
	compensate_no,
	MONTH,
	quantity,
	amount,
	reason,
	apply_user_name,
	apply_user_code,
	remark,
	act_status,
	STATUS,
	gmt_create,
	gmt_modify
FROM
	t_aac_shop_exempt_apply
WHERE
	1 = 1
AND exempt_no IN (?, ?)
AND exempt_no = ?
AND gmt_create >= ?
AND ? >= gmt_create
AND is_deleted = 0
LIMIT 10

        問題は、excludeNo に値がないのに、なぜこの判定条件を追加するのかということです。

<if test="exemptNo != null およびexcemptNo!=''"> およびexcempt_no = #{exemptNo} </if>

        このコレクションを走査するときに、ブロガーはコレクション要素のエイリアスに「exciteNo」という名前を付けました。そのため、mybatis はエイリアスと値をスペース オブジェクトに挿入し、次のスプライシングの判断でそれを使用しますか?

<if test="exemptNoList != null andexcemptNoList.size > 0">
    および
    <foreach collection="exemptNoList" item="exemptNo" open="("close=")" separator=","> # 
        {免除いいえ} 
    </foreach> 
</if>

ブロガーはエイリアスを変更しており、SQL スプライシングは正確です

select
        <include refid="Base_Column_List"/>
        from t_aac_shop_exempt_apply
        where 1 = 1
        <if test="exemptNoList != null and exemptNoList.size > 0">
            and exempt_no in
            <foreach collection="exemptNoList" item="exempt" open="(" close=")" separator=",">
                #{exempt}
            </foreach>
        </if>
        <if test="exemptNo != null and exemptNo!=''">
            and exempt_no = #{exemptNo}
        </if>
        <if test="applyUserCode != null and applyUserCode!=''">
            and apply_user_code = #{applyUserCode}
        </if>
        <if test="agentNo != null and agentNo!=''">
            and agent_no = #{agentNo}
        </if>
        <if test="serviceAgentNoList != null and serviceAgentNoList.size > 0">
            and agent_no in
            <foreach collection="serviceAgentNoList" item="agentNo" open="(" close=")" separator=",">
                #{agentNo}
            </foreach>
        </if>
        <if test="compensateNo != null and compensateNo!=''">
            and compensate_no = #{compensateNo}
        </if>
        <if test="month != null">
            and month = #{month}
        </if>
        <if test="status != null">
            and status = #{status}
        </if>
        <if test="statusList != null and statusList.size > 0">
            and status in
            <foreach collection="statusList" item="st" open="(" close=")" separator=",">
                #{st}
            </foreach>
        </if>
        <if test="startTime != null">
            and gmt_create <![CDATA[   >=  ]]> #{startTime}
        </if>
        <if test="endTime != null">
            and  #{endTime} <![CDATA[   >=  ]]> gmt_create
        </if>
        and is_deleted = 0
SELECT
	id,
	exempt_no,
	instance_code,
	agent_no,
	agent_name,
	compensate_no,
	MONTH,
	quantity,
	amount,
	reason,
	apply_user_name,
	apply_user_code,
	remark,
	act_status,
	STATUS,
	gmt_create,
	gmt_modify
FROM
	t_aac_shop_exempt_apply
WHERE
	1 = 1
AND exempt_no IN (?, ?)
AND gmt_create >= ?
AND ? >= gmt_create
AND is_deleted = 0
LIMIT 10

3. 原則

        理由がわかったら、mybatis のソース コードを見て、それがどのように機能するかを確認できますが、これは実際にはバグです。

ブレークポイントは、org.apache.ibatis.scripting.xmltags の DynamicSqlSource の getBoundSql メソッドにヒットします。

         extendNo が null 値であり、この条件を結合できないことがわかります。

                次に rootSqlNode.apply(context) を見てください。このステップが問題です。

@Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }

@Override
  public boolean apply(DynamicContext context) {
      判断是否拼接到sql
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }
public boolean evaluateBoolean(String expression, Object parameterObject) {
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value instanceof Boolean) {
      return (Boolean) value;
    }
    if (value instanceof Number) {
        return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);
    }
    return value != null;
  }

public static Object getValue(String expression, Object root) {
    try {
      Map<Object, OgnlClassResolver> context = Ognl.createDefaultContext(root, new OgnlClassResolver());
      return Ognl.getValue(parseExpression(expression), context, root);
    } catch (OgnlException e) {
      throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
    }
  }

        ここに焦点を当てます

@Override
  public boolean apply(DynamicContext context) {
    Map<String, Object> bindings = context.getBindings();
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    applyOpen(context);
    int i = 0;
    //将参数值进行遍历设置
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      if (first) {
        context = new PrefixedContext(context, "");
      } else if (separator != null) {
        context = new PrefixedContext(context, separator);
      } else {
          context = new PrefixedContext(context, "");
      }
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709 
      if (o instanceof Map.Entry) {
        @SuppressWarnings("unchecked") 
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
        applyIndex(context, i, uniqueNumber);
        //将别名与参数值存入键值对
        applyItem(context, o, uniqueNumber);
      }
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    applyClose(context);
    return true;
  }

        現時点では、mybatis が使用する DynamicContext オブジェクトの ContextMap キーと値のペアには 2 つのパラメーターしかありません。コレクションを走査するとき、applyItem メソッドはエイリアスをキーとして使用し、パラメーター値は ContextMap にmybatis が一時的に使用するための値ですが、使用後に削除されません。

        ここでは、mybatis によって定義された一般的なオブジェクトを確認できます。ContextMap は HashMap を継承し、mybatis によるスプライシング後の SQL パラメータと SQL キーと値のペアを格納するために使用されます。

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

  static {
    OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
  }

  private final ContextMap bindings;
  private final StringBuilder sqlBuilder = new StringBuilder();
  private int uniqueNumber = 0;

  public DynamicContext(Configuration configuration, Object parameterObject) {
    if (parameterObject != null && !(parameterObject instanceof Map)) {
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      bindings = new ContextMap(metaObject);
    } else {
      bindings = new ContextMap(null);
    }
    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
  }


static class ContextMap extends HashMap<String, Object> {
    private static final long serialVersionUID = 2977601501966151582L;

    private MetaObject parameterMetaObject;
    public ContextMap(MetaObject parameterMetaObject) {
      this.parameterMetaObject = parameterMetaObject;
    }

    @Override
    public Object get(Object key) {
      String strKey = (String) key;
      if (super.containsKey(strKey)) {
        return super.get(strKey);
      }

      if (parameterMetaObject != null) {
        // issue #61 do not modify the context when reading
        return parameterMetaObject.getValue(strKey);
      }

      return null;
    }
  }

         問題は明らかです。コレクションを走査するとき、エイリアスはキーと値のペアのマップに保存されます。これは 1 回限りの使用であるため、実際には不要です。

 4. まとめ

        mybatis のこの問題は、SQL のエイリアスを他のパラメータと同じにすることはできないことを示しています。これは一時的なものではありません。SQL に他のエラーがある場合、学生はブロガーの手順に従ってデバッグすることもできます。

おすすめ

転載: blog.csdn.net/m0_69270256/article/details/125910931