最近在项目使用 MyBatis 中碰到个问题,这个问题可能微不足道,但是还是拎出来讲一讲。
1 | <if test="type=='y'"> |
当传入的 type 的值为 y 的时候,if 判断内的 sql 不会执行。
抱着这个疑问就去看了 MyBatis 是怎么解析 sql 的。
下面我们一起来看一下MyBatis 的执行过程。
DefaultSqlSession.java 120行
1 |
|
executor.queryCursor(ms, wrapCollection(parameter), rowBounds); 点进去,是个接口,找实现类 BaseExecutor
执行到 BaseExecutor 的 queryCursor(
1 |
|
在 queryCursor 的方法中看到boundSql,是通过 ms.getBoundSql(parameter) 获取的
再点进去可以看到 MappedStatement 类中的getBoundSql方法
1 | public BoundSql getBoundSql(Object parameterObject) { |
看到有 sqlSource.getBoundSql(parameterObject),其中 sqlsource 是一个接口 Sqlsource 。
1 | public interface SqlSource { |
类中 getBoundSql 是一个核心方法,MyBatis 也是通过这个方法来为我们构建sql。
BoundSql 对象其中保存了经过参数解析,以及判断解析完成sql语句。
比如<if> <choose> <when>
都会在这一层完成,具体的完成方法往下看,Sqlsource 最常用的实现类是DynamicSqlSource。
1 | public class DynamicSqlSource implements SqlSource { |
核心方法是调用了rootSqlNode.apply(context),其中rootSqlNode是一个接口 SqlNode。
1 | public interface SqlNode { |
可以看到类中 rootSqlNode.apply(context) 的方法执行就是一个递归的调用,通过不同的实现类执行不同的标签,每一次apply 是完成了我们<> </>
一次标签中的sql创建,计算出标签中的那一段sql,MyBatis 通过不停的递归调用,来为我们完成了整个sql的拼接。那我们主要来看IF的实现类 IfSqlNode
1 | public class IfSqlNode implements SqlNode { |
可以看到IF的实现中,执行了 if (evaluator.evaluateBoolean(test, context.getBindings())) 如果返回是false的话直接返回,否则继续递归解析IF标签以下的标签,并且返回true。
那继续来看 evaluator.evaluateBoolean 的方法:
1 | public boolean evaluateBoolean(String expression, Object parameterObject) { |
关键点就在于这里,在OgnlCache.getValue中调用了Ognl.getValue,看到这里恍然大悟,mybatis是使用的OGNL表达式来进行解析的,在OGNL的表达式中,’y’ 会被解析成字符,因为java是强类型的,char 和 string 会导致不等,所以 if 标签中的sql不会被解析。
具体的请参照 OGNL 表达式的语法。
到这里,终于知道上面的问题是怎么回事了,只需要把代码修改成:(内双外单
)
1 | <if test='type=="y"'> |
也可以把代码修改成 ‘y’.toString()
1 | <if test="type == 'y'.toString()"> |
这样就解决了,虽然这个问题微不足道,但是有的人遇到了,一时还真的不知道该怎么办,这就提醒了大家一定要注意细节!