使用MyBatis的动态SQL表达式时遇到的“坑”(integer)
现有一项目,ORM框架使用的MyBatis,在进行列表查询时,选择一状态(值为0)通过动态SQL拼接其中条件但无法返回正常的查询结果,随后进行排查。
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时该SQL在and status = 0
并行正常拼接,也就是说测试的表达式为false
,从而导致查询结果错误。但是,显然该值(Integer:0)!= null也!='',应该为true
才对。
通过调试MyBatis源码顺藤摸瓜找到了IfSqlNode
类,该类用来处理动态SQL的<if>节点,方法public boolean apply(DynamicContext context)
用来构造节点内的SQL语句。if (evaluator.evaluateBoolean(test, context.getBindings())
该代码便是解析<if test="status !=null and status !=''">
测试内表达式的关键,如果表达式为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网官看看的英文不是我们的表达式语法有问题从而导致该问题的发生。
将对象解释为布尔值
任何对象都可以在需要布尔值的地方使用。OGNL将对象解释为这样的布尔值:
- 如果该对象是一个布尔值,则会提取并返回其值;
- 如果该对象是一个Number,则将其双精度浮点值与零进行比较; 非零被视为真,零为假;
- 如果对象是一个字符,那么它的布尔值是true,当且仅当它的char值非零时;
- 否则,当且仅当它非空时,其布尔值才为真。
果然,如果对象是一个数类型,值为0时将被解析为false
,否则为true
,浮点型0.00也是如此.OGNL对于布尔的定义和JavaScript的有点像,即'' == 0 == false
。也就这不难理解<if test="status != null and status !=''">and status = #{status}</if>
当状态= 0时出现的问题了,显然0!=''
的英文不成立的,导致表达式的值为假。
表达式将修改为<if test="status != null">and status = #{status}</if>
该问题便迎刃而解。该问题的根源还是来自编码的不规范,只有字符串类型才需要判断是否!=''
,其他类型完全没有这个必要,可能是开发人员为了省事直接复制上一行拿过来改一改或是所使用的MyBatis的生成工具不严谨导致该问题的发生。
这里有必要再提一个“坑”,如果有你类似于String str ="A";
<if test="str!= null and str == 'A'">
这样的写法时,你要小心了。单因为引号内如果为单个字符时,OGNL将会识别为的Java中的字符类型,显然字符串类型与焦炭做类型==
运算会报道查看false
,从而导致表达式不成立。解决方法很简单,为修改<if test='str!= null and str == "A"'>
即可。
作者:简单的土豆
链接:HTTP://www.jianshu.com/p/91ed365c0fdd
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
使用MyBatis的动态SQL表达式时遇到的“坑”(integer)的更多相关文章
- mybatis 的动态sql语句是基于OGNL表达式的。
mybatis 的动态sql语句是基于OGNL表达式的.可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类:1. if 语句 (简单的条件判断)2. c ...
- MyBatis的动态SQL详解
MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑,本文详解mybatis的动态sql,需要的朋友可以参考下 MyBatis 的一个强大的特性之一通常是它 ...
- mybatis 使用动态SQL
RoleMapper.java public interface RoleMapper { public void add(Role role); public void update(Role ro ...
- MyBatis框架——动态SQL、缓存机制、逆向工程
MyBatis框架--动态SQL.缓存机制.逆向工程 一.Dynamic SQL 为什么需要动态SQL?有时候需要根据实际传入的参数来动态的拼接SQL语句.最常用的就是:where和if标签 1.参考 ...
- MyBatis探究-----动态SQL详解
1.if标签 接口中方法:public List<Employee> getEmpsByEmpProperties(Employee employee); XML中:where 1=1必不 ...
- mybatis中的.xml文件总结——mybatis的动态sql
resultMap resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功. 如果sql查询字段名和pojo的属性名不一致,可以通过re ...
- MyBatis的动态SQL详解-各种标签使用
MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑. MyBatis中用于实现动态SQL的元素主要有: if choose(when,otherwise) ...
- Java-MyBatis:MyBatis 3 动态 SQL
ylbtech-Java-MyBatis:MyBatis 3 动态 SQL 1.返回顶部 1. 动态 SQL MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其它类似框架 ...
- Mybatis解析动态sql原理分析
前言 废话不多说,直接进入文章. 我们在使用mybatis的时候,会在xml中编写sql语句. 比如这段动态sql代码: <update id="update" parame ...
随机推荐
- ScrollView 滚动视图
ScrollView 种类: 1.HorizontalScrollView:水平滚动视图 2.ScrollView:垂直滚动视图(常用类) public class MainActivity exte ...
- Hadoop_11_HDFS的流式 API 操作
对于MapReduce等框架来说,需要有一套更底层的API来获取某个指定文件中的一部分数据,而不是一整个文件 因此使用流的方式来操作 HDFS上的文件,可以实现读取指定偏移量范围的数据 1.客户端测试 ...
- View相关面试问题-View绘制面试问题详解
View树的绘制流程: measure() --> layout() --> onDraw(),具体使用这个可以参考之前实现的自定义的博客:[http://www.cnblogs.co ...
- Blend Visual studio 和Visual studio 的区别
blend for visual studio 这个并不是用于开发中写代码的,而是专门用来做WPF的界面设计的.这是为了让专业的人做专业的事,尽量让写代码的人只使用visual studio,而做界面 ...
- gitlab断电
断电重启以后 会出现2019-06-26_05:47:42.71382 HINT: If you're sure there are no old server processes still run ...
- HTML 获取class里的多个值 和 dataset的使用
属性:classList var x = event.target; console.log(x); console.log(x.classList); 可以发现 获取了全部的class里的值 获取单 ...
- OkHttp3-基本用法(转)
OkHttp 一个支持Http和Http/2,可适用于Android以及Java应用的网络请求客户端. 概述 Http是现代网络应用的所常用的协议,它是一种数据传输的媒介.执行高效的Http代码可以让 ...
- 【JZOJ5801】【2018.8.12省选模拟】circular
题目大意 分析 把环拆开 线段其实就是区间 对于每个区间,向在TA后面并且b_i最小的区间连边, 然后从每个区间(ai,bi)开始,在保证跳到的区间(aj,bj),bj<=ai+m的情况下向后倍 ...
- CentOS 7下安装IDL 8.2
材料准备: CentOS 7 x86_64 IDL 8.2,下载自 ftp://ftp.lowell.edu/incoming/temp/old/IDL_Clients/v8.2/idl82sp2li ...
- ibatis和mybatis中的BatchExecutor
ibatis中的的处理方法 spring集成了ibatis的批量提交的功能,我们只要调用API就可以了 首先在你的dao中需要继承org.springframework.orm.ibatis.supp ...