Edited at

MyBatisの条件分岐(<if>など)で文字列リテラルを利用する際の注意点

現場でハマっていた人がいたので・・・メモっておきます。


ハマっていた事象は?

パラメータオブジェクトのプロパティ値(String型)に応じて組み立てるSQLを変更するために、MyBatis提供の <if> タグ を使用して以下のような条件分岐を組んでいた。


パラメータオブジェクト

public class MyParam {

private String status;
// ...
}


マッパーXML(断片)

<if test="status == '0'">

...
</if>

開発者の想いとしては・・・ statusの値が"0" の場合に特定の条件を加えたかったのですが・・・実際にはこの条件が true になることはなかったのです。


なぜか?

MyBatisは test属性に指定された値をOGNL式として評価するのですが、OGNL式的には'0'StringではなくCharacterとして扱うようで、型が異なる値の比較となり常に false になってしまうというオチでした。

気になるのは・・・この動作は仕様なのか?という部分ですが・・・

MyBatisが利用しているOGNLライブラリの言語リファレンスを見ると・・・・



  • String literals, as in Java (with the addition of single quotes): delimited by single- or double-quotes, with the full set of character escapes.

  • Character literals, also as in Java: delimited by single-quotes, also with the full set of escapes.


と記載がありました。明示はされていませんが・・・



  • '0' -> Character


  • '00' -> String


  • "0" -> String


  • "00" -> String (当たり前ですが・・)

ということなのかなと・・・。


じゃあ・・・ダブルクォーテーションで括る? →


マッパーXML(断片)

<if test="status == "0"">

...
</if>

皆様ご存知の通り・・・(当たり前なのでが・・・)上記の記載はXMLとして成立しません。でも「ダブルクォーテーションで括る」というアプローチは間違ってません。


XML属性の括り文字を変えればいんじゃね!? →

色々解決方法はあるのですが・・・わたしが直感的かな〜と思ったのは・・・XML属性の括り文字を「"」から「'」にし、test属性の値の中で「"」を使えるようにするというものでした。


マッパーXML(断片)

<if test='status == "0"'>

...
</if>


他の解決方法は?

もちろん他にも解決方法はあります。


定数の利用

文字列リテラルを直接指定せず、定数化してOGNL式の中で参照することができます。

実は・・・この方法があるべき姿なのかもしれませんね。


定数クラス

package com.example.domain;

public class Status {
public static final String STOP = "0";
// ...
}


マッパーXML(断片)

<if test="status == @com.example.domain.Status@STOP">

...
</if>


実体参照の利用

文字列リテラルの括り文字を「"」の実体参照「&quot;」にすることでOGNL式側には「"0"」と認識させることができます。ただし・・・見てわかる通り・・・可読性はかなり低くなります。


マッパーXML(断片)

<if test="status == &quot;0&quot;">

...
</if>


toStringの利用

OGNL式はメソッドを呼び出すことができるので、Character#toString()を呼び出してStirngへ変換することができます。う〜ん・・・、動くけどなんか負けた感が半端ない・・・


マッパーXML(断片)

<if test="status == '0'.toString()">

...
</if>


まとめ

ちゃんと定数設計ができていれば、定数を利用するのが良さげですが・・・今回は「文字列リテラルを利用する前提」でエントリーを書いたので・・・「XML属性の括り文字を「'」にする」方法をイチオシにしました。

ちなみに・・・ハマっていたエンジニアは「toStringの利用」での解決方法をネットで見つけて修正案として提案してきたんですけどね・・・