The "pit" encountered when using MyBatis dynamic SQL expressions

There is an existing project. The ORM framework uses MyBatis. When performing a list query, a state (value 0) is selected and the where condition is spliced ​​through dynamic SQL, but the normal query results cannot be returned, and then the investigation is carried out.

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>

When statusthe value of 0 is 0, the where SQL is and status = 0not spliced ​​normally, that is to say, the expression in test is false, resulting in incorrect query results. However, obviously the value (Integer: 0)! = null also! = ' ', it should be truecorrect.

Through the Debug MyBatis source code, I found the IfSqlNodeclass, which is used to process the <if> node of dynamic SQL, and the method is public boolean apply(DynamicContext context)used to construct the SQL statement in the node. if (evaluator.evaluateBoolean(test, context.getBindings())This code is the key to parsing <if test="status !=null and status !=''">the expression in the test. If the expression is true, the SQL is spliced, otherwise it is ignored.

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

Open the ExpressionEvaluator class and find that OGNL is used for parsing expressions. If you have used the ancient Struts framework, you should be familiar with it. It OgnlCache.getValue(expression, parameterObject);can be seen that the value of the expression is obtained from the cache, which shows that MyBatis has also cached the expression to improve performance.

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

I followed up and finally found a way to parse the expression private static Object parseExpression(String expression). This method will first get the value from the cache, if not, it will be parsed and put into the cache, and then called Ognl.getValue(parseExpression(expression), root)to get the value of the expression.

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

As Ognl.getValue(parseExpression(expression), root)for how it works, if you are interested, you can follow along and find out, and this article will not go into details. So far, we already know that MyBatis expressions are processed with OGNL, which is enough. Next, let's go to the OGNL official website to see if there is a problem with our expression syntax that causes this problem.

Interpreting Objects as Booleans

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

  1. If the object is a Boolean, its value is extracted and returned;
  2. 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;
  3. If the object is a Character, its boolean value is true if and only if its char value is non-zero;
  4. Otherwise, its boolean value is true if and only if it is non-null.

Sure enough, if the object is of type Number, a value of 0 will be parsed as 0 false, otherwise it will be true0.00 as a float. OGNL's definition of boolean is a bit like JavaScript, ie '' == 0 == false. It is not difficult to understand <if test="status != null and status !=''">and status = #{status}</if>the problem that occurs when status=0, which obviously 0!=''does not hold, resulting in the value of the expression being false.

Modifying the expression to <if test="status != null">and status = #{status}</if>this problem solves the problem. The root of the problem is still from the non-standard coding . Only the String type needs to judge whether it !=''is not necessary at all. It is not necessary for other types at all. It may be that the developer directly copied the previous line to change it or the MyBatis generation tool used was not necessary. Rigorousness causes this problem to occur.

It is necessary to mention another "pit" here. If you have String str ="A"; <if test="str!= null and str == 'A'">a writing method similar to this, you have to be careful. Because if there is a single character in the single quotation mark , OGNL will recognize it as the char type in Java. Obviously, the operation of String type and char type ==will return false, which will cause the expression to fail. The solution is very simple, you can modify it <if test='str!= null and str == "A"'>to.


Author: Simple Potatoes
Link: http://www.jianshu.com/p/91ed365c0fdd
Source: Jianshu
Copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326490738&siteId=291194637