博客
关于我
源码解析之 Mybatis 对 Integer 参数做了什么手脚?
阅读量:439 次
发布时间:2019-03-06

本文共 8774 字,大约阅读时间需要 29 分钟。

源码解析之 Mybatis 对 Integer 参数做了什么手脚?

问题描述

在 Mybatis 中,当 Integer 入参为 0 时,发现判断条件的非空判断没有生效,原本应该存在的判断条件丢失了。这意味着 Mybatis 对 Integer 参数进行了某种特殊处理,导致 0 被误判为有效值。

接口示例

@GetMapping("/queryByAgeGroup")
public HttpStatus queryByAgeGroup(@RequestParams("ageGroup") Integer ageGroup) {
IndexTestService.queryByAgeGroup(ageGroup);
return HttpStatus.HTTP_OK;
}

查询 SQL 示例

数据表结构示例

CREATE TABLE `people_info` (
`id` varchar(64) NOT NULL COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '名称',
`age_group` int(11) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

环境示例

当入参为 0 时,生成的 SQL 为:

select *from `people_info`where 1 = 1-- 本该存在的 ageGroup 判断消失了!

源码解析

解析过程

  • DefaultSqlSession#select

    @Override
    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();
    }
    }
  • BaseExecutor#query

    @Override
    public List
    query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql, 0);
    }
  • MappedStatement#getBoundSql

    public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List
    parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
    boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }
    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

    public interface SqlSource {
    BoundSql getBoundSql(Object parameterObject);
    }
  • DynamicSqlSource#getBoundSql

    @Override
    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);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
    }
  • SqlNode#apply

    public interface SqlNode {
    boolean apply(DynamicContext context);
    }
  • IfSqlNode#apply

    @Override
    public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
    contents.apply(context);
    return true;
    }
    return false;
    }
  • ExpressionEvaluator#evaluateBoolean

    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)).compareTo(BigDecimal.ZERO) != 0;
    }
    return value != null;
    }
  • OgnlCache#getValue

    public static Object getValue(String expression, Object root) {
    try {
    Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
    return Ognl.getValue(parseExpression(expression), context, root);
    } catch (OgnlException e) {
    throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
    }
    }
  • Ognl#getValue

    public static Object getValue(Object tree, Map context, Object root) throws OgnlException {
    return getValue(tree, context, root, null);
    }
  • TypeConverter#convertValue

    public interface TypeConverter {
    public Object convertValue(Map context, Object target, Member member, String propertyName, Object value, Class toType);
    }
  • DefaultTypeConverter#convertValue

    public Object convertValue(Map context, Object value, Class toType) {
    return OgnlOps.convertValue(value, toType);
    }
  • OgnlOps#convertValue

    public static Object convertValue(Object value, Class toType) {
    return convertValue(value, toType, false);
    }
  • OgnlOps#convertValue

    public static Object convertValue(Object value, Class toType, boolean preventNulls) {
    Object result = null;
    if (value != null && toType.isAssignableFrom(value.getClass())) {
    return value;
    }
    if (value != null) {
    if (value.getClass().isArray() && toType.isArray()) {
    result = Array.newInstance(toType.getComponentType(), Array.getLength(value));
    for (int i = 0, icount = Array.getLength(value); i < icount; i++) {
    Array.set(result, i, convertValue(Array.get(value, i), toType.getComponentType()));
    }
    } else if (value.getClass().isArray() && !toType.isArray()) {
    return convertValue(Array.get(value, 0), toType);
    } else if (!value.getClass().isArray() && toType.isArray()) {
    if (toType.getComponentType() == Character.TYPE) {
    result = stringValue(value).toCharArray();
    } else if (toType.getComponentType() == Object.class) {
    if (value instanceof Collection) {
    return ((Collection) value).toArray(new Object[0]);
    } else {
    return new Object[] { value };
    }
    }
    }
    if ((toType == Integer.class) || (toType == Integer.TYPE)) {
    result = new Integer((int) longValue(value));
    }
    if ((toType == Double.class) || (toType == Double.TYPE)) {
    result = new Double(doubleValue(value));
    }
    if ((toType == Boolean.class) || (toType == Boolean.TYPE)) {
    result = booleanValue(value) ? Boolean.TRUE : Boolean.FALSE;
    }
    if ((toType == Byte.class) || (toType == Byte.TYPE)) {
    result = new Byte((byte) longValue(value));
    }
    if ((toType == Character.class) || (toType == Character.TYPE)) {
    result = new Character((char) longValue(value));
    }
    if ((toType == Short.class) || (toType == Short.TYPE)) {
    result = new Short((short) longValue(value));
    }
    if ((toType == Long.class) || (toType == Long.TYPE)) {
    result = new Long(longValue(value));
    }
    if ((toType == Float.class) || (toType == Float.TYPE)) {
    result = new Float(doubleValue(value));
    }
    if (toType == BigInteger.class) {
    result = bigIntValue(value);
    }
    if (toType == BigDecimal.class) {
    result = bigDecValue(value);
    }
    if (toType == String.class) {
    result = stringValue(value);
    }
    } else {
    if (toType.isPrimitive()) {
    result = OgnlRuntime.getPrimitiveDefaultValue(toType);
    } else if (preventNulls && toType == Boolean.class) {
    result = Boolean.FALSE;
    } else if (preventNulls && Number.class.isAssignableFrom(toType)) {
    result = OgnlRuntime.getNumericDefaultValue(toType);
    }
    }
    if (result == null && preventNulls) {
    return value;
    }
    if (value != null && result == null) {
    throw new IllegalArgumentException("Unable to convert type " + value.getClass().getName() + " of " + value + " to type of " + toType.getName());
    }
    return result;
    }
  • OgnlOps#longValue

    public static long longValue(Object value) throws NumberFormatException {
    if (value == null) return 0L;
    Class c = value.getClass();
    if (c.getSuperclass() == Number.class) return ((Number) value).longValue();
    if (c == Boolean.class) return ((Boolean) value).booleanValue() ? 1 : 0;
    if (c == Character.class) return ((Character) value).charValue();
    return Long.parseLong(stringValue(value, true));
    }
  • 结论

    Mybatis 在处理 Integer 参数时,会将其转换为 Long 类型。具体来说,当 Integer 参数为 0 时,会被转换为 Long 类型的 0L。这种转换导致在非空判断中,0 被误判为有效值,从而使得原本应该存在的判断条件丢失。

    因此,当使用 Mybatis 的非空判断时,若参数为 Integer 类型且为 0,会被误判为有效值。需要在条件判断中特别处理 0,或者确保参数类型不会被不正确地转换。

    此外,类似的类型转换问题也会影响 Double、Byte、Character、Short、Long、Float、BigInteger 和 BigDecimal 等其他数值类型的参数。

    转载地址:http://iusyz.baihongyu.com/

    你可能感兴趣的文章
    nodejs libararies
    查看>>
    nodejs-mime类型
    查看>>
    nodejs中Express 路由统一设置缓存的小技巧
    查看>>
    Node入门之创建第一个HelloNode
    查看>>
    NotImplementedError: Cannot copy out of meta tensor; no data! Please use torch.nn.Module.to_empty()
    查看>>
    npm run build 失败Compiler server unexpectedly exited with code: null and signal: SIGBUS
    查看>>
    npm WARN deprecated core-js@2.6.12 core-js@<3.3 is no longer maintained and not recommended for usa
    查看>>
    npm和yarn的使用对比
    查看>>
    npm报错unable to access ‘https://github.com/sohee-lee7/Squire.git/‘
    查看>>
    npm的问题:config global `--global`, `--local` are deprecated. Use `--location=global` instead 的解决办法
    查看>>
    NR,NF,FNR
    查看>>
    nrf开发笔记一开发软件
    查看>>
    NSDateFormatter的替代方法
    查看>>
    NSOperation基本操作
    查看>>
    NSSet集合 无序的 不能重复的
    查看>>
    NT AUTHORITY\NETWORK SERVICE 权限问题
    查看>>
    ntko文件存取错误_苹果推送 macOS 10.15.4:iCloud 云盘文件夹共享终于来了
    查看>>
    nullnullHuge Pages
    查看>>
    numpy 用法
    查看>>
    Numpy如何使用np.umprod重写range函数中i的python
    查看>>