mybatis 中 if-test 判斷大坑


【<if test="takeWay == '0'">】mybatis的if判斷

單個的字符要寫到雙引號里面才行,改為<if test='takeWay == "1"'>或者改為<if test="takeWay == '1'.toString() ">

.xml文件的部分代碼

復制代碼
<insert id="insertDelivery" parameterType="com.zuci.request.DeliveryPreferenceReq">     
    insert cx_customer_deliverypreference     
    <trim prefix="(" suffix=")" suffixOverrides=",">           
        .... 此處省略       
        <if test="takeWay == '1' and workday != null ">         
            WORKDAY,       
        </if>       
        ....     
    </trim>
         
    <trim prefix="values (" suffix=")" suffixOverrides=",">          
        .... 此處省略          
        <if test="takeWay == '1' and workday != null ">            
            #{workday, jdbcType=VARCHAR},     
        </if>      
        ....   
    </trim>
</insert>
復制代碼

takeWay == “1”處出錯,導致不執行if判斷中的sql,運行程序不報錯,沒有任何提示。去掉takeWay == “1” and 則可執行。對此我百思不得其解,

因為自己有寫過如下代碼,是沒錯的。

<if test="messageType == 'senderReceiveSuccess' ">
      ......
</if>

把<if test="takeWay == '1' and workday != null ">

改為<if test='takeWay == "1" and workday != null '>

或改為<if test="takeWay == '1'.toString() and workday != null ">即可。

原因是:mybatis是用OGNL表達式來解析的,在OGNL的表達式中,’1’會被解析成字符,java是強類型的,char 和 一個string 會導致不等,所以if標簽中的sql不會被解析。

總結下使用方法:單個的字符要寫到雙引號里面或者使用.toString()才行!

 

使用Mybatis時,常常會判斷屬性是否為空

POJO

private Integer status;//狀態,可能為0、1、2、3。

Mapper XML

<sql>
  <trim prefix="where" prefixOverrides="and | or ">
      //...省略其他
      <if test="status != null and status !=''">and status = #{status}</if> 
  <trim prefix="where" prefixOverrides="and | or ">
</sql>

status的值為 0時該where SQL and status = 0並未正常拼接,也就是說test內的表達式為false,從而導致查詢結果錯誤。但是,顯然該值(Integer :0)!= null也!= ' ',應該為true才對。

當status為Integer類型,並且status值為0時,該if判斷卻為false。

當status為0時,Mybatis會解析成'' 空字符串。

為了避免這個問題,改成下面這樣寫,去掉對空字符的判斷,就解決了該問題

<if test="status != null">and status = #{status}</if> 

 

原因分析

通過Debug MyBatis源碼順藤摸瓜找到了 IfSqlNode類,該類用來處理動態SQL的<if>節點,方法 public boolean apply(DynamicContext context)用來構造節點內的SQL語句。 if (evaluator.evaluateBoolean(test, context.getBindings())該代碼便是解析 <if test="status !=null and status !=''">test內表達式的關鍵,如果表達式為true則拼接SQL,否則忽略。
復制代碼
public class IfSqlNode implements SqlNode {
  private ExpressionEvaluator evaluator;
  private String test;
  private SqlNode contents;

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

  public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }
}
復制代碼
打開ExpressionEvaluator 類,發現解析表達式使用的是OGNL,如果你使用過古老的Struts框架你應該對它不陌生。通過 OgnlCache.getValue(expression, parameterObject);可以看到表達式的值是從緩存中獲取的,由此可知MyBatis竟然對表達式也做了緩存,以提高性能。
復制代碼
public class ExpressionEvaluator {  
  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;  
  }  
復制代碼
跟進去看看,終於找到了解析表達式的方法 private static Object parseExpression(String expression),該方法會先從緩存取值,如果沒有便進行解析並放入緩存中,然后調用 Ognl.getValue(parseExpression(expression), root)獲得表達式的值。
復制代碼
public class OgnlCache {

  private static final Map<String, ognl.Node> expressionCache = new ConcurrentHashMap<String, ognl.Node>();

  public static Object getValue(String expression, Object root) throws OgnlException {
    return Ognl.getValue(parseExpression(expression), root);
  }

  private static Object parseExpression(String expression) throws OgnlException {
    try {
      Node node = expressionCache.get(expression);
      if (node == null) {
        node = new OgnlParser(new StringReader(expression)).topLevelExpression();
        expressionCache.put(expression, node);
      }
      return node;
    } catch (ParseException e) {
      throw new ExpressionSyntaxException(expression, e);
    } catch (TokenMgrError e) {
      throw new ExpressionSyntaxException(expression, e);
    }
  }
復制代碼
至於 Ognl.getValue(parseExpression(expression), root)是如何運作的,如果你有興趣可以自行跟下去一探究竟,本文就不贅述了。到此為止,我們已經知道MyBatis的表達式是用OGNL處理的了,這一點已經夠了。下面我們去 OGNL官網看看是不是我們的表達式語法有問題從而導致該問題的發生。

Interpreting Objects as Booleans

Any object can be used where a boolean is required. OGNL interprets objects as booleans like this:

  • If the object is a Boolean, its value is extracted and returned;
  • If the object is a Number, its double-precision floating-point value is compared with zero; non-zero is treated as true, zero as false;
  • If the object is a Character, its boolean value is true if and only if its char value is non-zero;
  • Otherwise, its boolean value is true if and only if it is non-null.

果然,如果對象是一個Number類型,值為0時將被解析為false,否則為true,浮點型0.00也是如此。OGNL對於boolean的定義和JavaScript有點像,即'' == 0 == false。這也就不難理解<if test="status != null and status !=''">and status = #{status}</if>當status=0時出現的問題了,顯然0!=''是不成立的,導致表達式的值為false。

將表達式修改為<if test="status != null">and status = #{status}</if>該問題便迎刃而解。該問題的根源還是來自編碼的不規范,只有String類型才需要判斷是否!='',其他類型完全沒有這個必要,可能是開發人員為了省事直接復制上一行拿過來改一改或是所使用的MyBatis生成工具不嚴謹導致該問題的發生。

這里有必要再提一個“坑”,如果你有類似於String str ="A"; <if test="str!= null and str == 'A'">這樣的寫法時,你要小心了。因為單引號內如果為單個字符時,OGNL將會識別為Java 中的 char類型,顯然String 類型與char類型做==運算會返回false,從而導致表達式不成立。解決方法很簡單,修改為<if test='str!= null and str == "A"'>即可。

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM