2016年8月28日 每周一篇博客
在项目中xml文件经常会遇到在判断等于某个值时加什么条件不等于某个值的时候加什么条件,比如下面这个例子:
<if test=" name != null">
AND T.NAME = #{NAME,jdbcType=VARCHAR}
</if>
<if test=" name == null">
ORDER BY NAME,ID
</if>
正确很好的写法需要引入<choose>标签
<choose>
<when test=" name != null">
AND T.NAME = #{NAME,jdbcType=VARCHAR}
</when>
<otherwise>
ORDER BY T.PRODUCT_TYPE_CODE, T.SORT DESC, T.CREATE_TIME
</otherwise>
</choose>
第一种错误写法导致的结果就是不会去做任何判断即使name不为空。为什么只能用<choose>标签,源码还没有研究,或者我这个例子本身就有问题现在记录下来,在后续的更新中我会再次总结一下这个问题。
2.
<!--错误的写法-->
<if test="newsImage != null and newsImage == 'y'">
<![CDATA[ and len(newsImage) > 0 ]]>
</if>
<!-- 正确的,稳定,推荐使用 -->
<if test="newsImage != null and newsImage == 'y'.toString()">
<![CDATA[ and len(newsImage) > 0 ]]>
</if>
判断 newsImage == 'y' 时,有人认为成功,但实际上是不成功的,需要改为 newsImage == 'y'.toString()方可成功,原因具体没
有细入研究,根据实际使用推测应该是 “等于” 在java中是个比较复杂问题,涉及的“等于”有可能是变量地址相等,或者是变量值内
容相等,在XML文件中简单的 == 在经过MyBatis处理后无法判断是哪种类型的“相等”,所以加.toString()做强制转换操作,MyBatis
就知道是值内容的比较,当然就成功了;注意这个常量不限于数字,对于字母,如 'y' 同样需要加上 .toString()方可成功。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
最近在项目使用mybatis中碰到个问题
Xml代码
- <if test="type=='y'">
- and status = 0
- </if>
当传入的type的值为y的时候,if判断内的sql也不会执行,抱着这个疑问就去看了mybatis是怎么解析sql的。下面我们一起来看一下mybatis 的执行过程。
DefaultSqlSession.class 121行
Java代码
- public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
- try {
- MappedStatement ms = configuration.getMappedStatement(statement);
- executor.query(ms, wrapCollection(parameter), rowBounds, handler);
- } catch (Exception e) {
- throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
- } finally {
- ErrorContext.instance().reset();
- }
- }
在 executor.query(ms, wrapCollection(parameter), rowBounds, handler);
执行到BaseExecutor.class执行器中的query方法
Java代码
- public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
- BoundSql boundSql = ms.getBoundSql(parameter);
- CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
- return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
- }
在query的方法中看到boundSql,是通过 ms.getBoundSql(parameter);获取的。
再点进去可以看到MappedStatement.class类中的getBoundSql方法
Java代码
- public BoundSql getBoundSql(Object parameterObject) {
- BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
- List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
- if (parameterMappings == null || parameterMappings.size() <= 0) {
- boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
- }
- // check for nested result maps in parameter mappings (issue #30)
- for (ParameterMapping pm : boundSql.getParameterMappings()) {
- String rmId = pm.getResultMapId();
- if (rmId != null) {
- ResultMap rm = configuration.getResultMap(rmId);
- if (rm != null) {
- hasNestedResultMaps |= rm.hasNestedResultMaps();
- }
- }
- }
- return boundSql;
- }
看到其中有sqlSource.getBoundSql(parameterObject); sqlsource是一个接口。
Java代码
- /**
- *
- * This bean represets the content of a mapped statement read from an XML file
- * or an annotation. It creates the SQL that will be passed to the database out
- * of the input parameter received from the user.
- *
- */
- public interface SqlSource {
- BoundSql getBoundSql(Object parameterObject);
- }
类中getBoundSql是一个核心方法,mybatis 也是通过这个方法来为我们构建sql。BoundSql 对象其中保存了经过参数解析,以及判断解析完成sql语句。比如<if> <choose> <when> 都回在这一层完成,具体的完成方法往下看,那最常用sqlSource的实现类是DynamicSqlSource.class
Java代码
- public class DynamicSqlSource implements SqlSource {
- private Configuration configuration;
- private SqlNode rootSqlNode;
- public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
- this.configuration = configuration;
- this.rootSqlNode = rootSqlNode;
- }
- public BoundSql getBoundSql(Object parameterObject) {
- DynamicContext context = new DynamicContext(configuration, parameterObject);
- rootSqlNode.apply(context);
- SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
- Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
- SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
- BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
- for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
- boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
- }
- return boundSql;
- }
- }
核心方法是调用了rootSqlNode.apply(context); rootSqlNode是一个接口
Java代码
- public interface SqlNode {
- boolean apply(DynamicContext context);
- }
可以看到类中 rootSqlNode.apply(context); 的方法执行就是一个递归的调用,通过不同的
实现类执行不同的标签,每一次appll是完成了我们<></>一次标签中的sql创建,计算出标签中的那一段sql,mybatis通过不停的递归调用,来为我们完成了整个sql的拼接。那我们主要来看IF的实现类IfSqlNode.class
Java代码
- 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;
- }
- }
可以看到IF的实现中,执行了 if (evaluator.evaluateBoolean(test, context.getBindings())) 如果返回是false的话直接返回,否则继续递归解析IF标签以下的标签,并且返回true。那继续来看 evaluator.evaluateBoolean 的方法
Java代码
- 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;
- }
关键点就在于这里,在OgnlCache.getValue中调用了Ognl.getValue,看到这里恍然大悟,mybatis是使用的OGNL表达式来进行解析的,在OGNL的表达式中,'y'会被解析成字符,因为java是强类型的,char 和 一个string 会导致不等。所以if标签中的sql不会被解析。具体的请参照 OGNL 表达式的语法。到这里,上面的问题终于解决了,只需要把代码修改成:
Xml代码
- <if test='type=="y"'>
- and status = 0
- </if>
就可以执行了,这样"y"解析出来是一个字符串,两者相等!