MyBatis查询的意外问题
使用MyBatis的注解写sql,像往常一样加入条件查询,如下:
"<if test=\"type!=null and type!=2\"> and (show_position=#{type,jdbcType=TINYINT} or show_position=2)</if>",
"<if test=\"type!=null and type=1\"> ORDER BY seq ASC</if>",
代码里注入参数,type是Byte类型的0.
执行时发生了什么?
==> Parameters: 1(Integer)
两条if都命中了,type实际注入的值是1
???
调试过程发现了一点问题,这些sql经过代理和切面的处理,最终将转化为预编译语句(PreparedStatement)。而注入参数的时候,一方面可以看到由方法注入的参数(代码传进去的参数),另一方面有一个BoundSql对象。注入参数会优先取BoundSql中的additional parameter。而在判断type!=null and type=1
的时候,处理器认为这是一个赋值语句,其返回值是1,而不是false,并且也写到了BoundSql中的additional parameter,这就导致实际执行的sql type值为1.
如果是判断相等,这里应该使用==
来判断,即可解决问题
附上一些代码:
DefaultParameterHandler
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
//******这里 优先从additional parameter取得参数值。因此实际type取得值是1
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
//最后的else从parameter object取,也就是传递给方法的参数
} else {
value = metaObject == null ? null : metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
//编译PreparedStatement,并且注入参数,通过handler.parameterize方法
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
BoundSql生成的地方:MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
//sqlSource的类型是DynamicSqlSource
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;
}
DynamicSqlSource
public BoundSql getBoundSql(Object parameterObject) {
//DynamicContext里面有一个bindings属性,这里只有两个参数:
//_parameter ==> parameter object(map)
//_databaseId ==> MySQL
DynamicContext context = new DynamicContext(configuration, parameterObject);
//apply方法中进行所有if脚本的解析
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;
}
MixedSqlNode
public boolean apply(DynamicContext context) {
//contents里面是各种SqlNode 比如IfSqlNode TextSqlNode等
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
IfSqlNode
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
//这里的contents是<if>和</if>里面的语句。如果条件成立则把这些内容加入到context中
contents.apply(context);
return true;
}
return false;
}
ExpressionEvaluator
public boolean evaluateBoolean(String expression, Object parameterObject) {
//就这个东西,不知道是啥,很魔性
//如果是一个赋值语句,会把那个属性和值直接放到parameterObject里面
//试了一把,类似于java表达式,他可以解析表达式的值
//例如 type=1 返回值是1 type==1是false
//parameterObject的作用就是提供参数值作比较
Object value = OgnlCache.getValue(expression, parameterObject);
//如果本身返回值是boolean 直接返回结果
if (value instanceof Boolean) return (Boolean) value;
//如果返回值是数字类型 那么非0为true 0为false 和常见的一些规则也是符合的
if (value instanceof Number) return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);
//如果返回值是其他类型 那么非空为true 空为false
return value != null;
}