【方向盘】MyBatis封装结果集时,Integer类型的id字段被赋值成了Long类型---读源码找原因(中)

简介: 【方向盘】MyBatis封装结果集时,Integer类型的id字段被赋值成了Long类型---读源码找原因(中)

这里面非常重要的一个方法applyPropertyMappings


private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        // issue #377, call setter on nulls
        if (value != DEFERED
            && property != null
            && (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()))) {
          metaObject.setValue(property, value);
        }
        if (property != null && (value != null || value == DEFERED)) {
          foundValues = true;
        }
      }
    }
    return foundValues;
  }


其实在这里可以窥视到从数据表的列如何映射到对象的属性的一点端倪了:


      先把resultMap中取得的列名转换为大写字母,再截取它的前缀(去除特殊字符),把这个前缀和要映射到的对象的属性进行比对,符合的就映射过去,即对POJO对象注入对应属性值。所以这里是不受到字母大小写的影响的


image.png


从此处需要注意了,for循环里已经按照数据库表列为维度,一个一个的处理了。这里面有一行代码必要详细看一下:


 Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);


还记得我们最前面说的Id被赋值为BigInteger了吗?所以我猜测就是这里的value值自己本身就是BigInteger的类型了,看看getPropertyMappingValue咋写的:


private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) {
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
      addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
      return DEFERED;
    } else {
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      return typeHandler.getResult(rs, column);
    }
  }


到了这一步其实我就比较更为熟悉了。


image.png


调试看到了这个,思路就越来越清晰了。很显然,就是处理转换的类型转换器竟然是UnKonownTypeHandler,所以给我们转换成了什么鬼呢?为了一探究竟我们跟踪到它的getNullableResult方法:


 @Override
  public Object getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    TypeHandler<?> handler = resolveTypeHandler(rs, columnName);
    return handler.getResult(rs, columnName);
  }
private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) {
    try {
      Map<String,Integer> columnIndexLookup;
      columnIndexLookup = new HashMap<String,Integer>();
      ResultSetMetaData rsmd = rs.getMetaData();
      int count = rsmd.getColumnCount();
      for (int i=1; i <= count; i++) {
        String name = rsmd.getColumnName(i);
        columnIndexLookup.put(name,i);
      }
      Integer columnIndex = columnIndexLookup.get(column);
      TypeHandler<?> handler = null;
      if (columnIndex != null) {
        handler = resolveTypeHandler(rsmd, columnIndex);
      }
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
      return handler;
    } catch (SQLException e) {
      throw new TypeException("Error determining JDBC type for column " + column + ".  Cause: " + e, e);
    }
  }


image.png


看到问题的又一根源了,MyBatis完全根据数据库中id字段的类型来推断Java类型,而这种推断又依赖于这部分代码


image.png


这是非常不好的一种处理方式,因为Map里面的值竟然采用自然排序,然后通过index去识别,显然就非常有问题。


所以我们看到的现象是,有的有问题,有的没有问题。有的问题的方式并且都不尽相同,有的成了Long,有的成了BigInteger


我个人认为这是MyBatis设计另一个很失败的地方,可以定义为一个bug级别的存在。关键它还是“软病”,让我着实花了好久找到此处。后续希望自己可以提个issue被采纳


那我们看到了此处被选中的为BigInteger的转换器,所以自然而然得到的值类型如下:


image.png


所以,最直接的问题,我们只剩下一个了,为何BigInteger类型的值,可以被set到Integer类型的Id上面。那就继续跟踪这句代码:


metaObject.setValue(property, value);


image.png


setValue方法如下:


  public void setValue(String name, Object value) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        if (value == null && prop.getChildren() != null) {
          // don't instantiate child path if value is null
          return;
        } else {
          metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
        }
      }
      metaValue.setValue(prop.getChildren(), value);
    } else {
      objectWrapper.set(prop, value);
    }
  }


执行了objectWrapper.set(prop, value);这一句,调用的objectWrapper的方法。这里其实会引申出好几个问题,objectWrapper怎么来的?暂时不讨论这快,接着往下走吧。调试发现objectWrapper其实是个BeanWrapper,所以我们点进去看看它的set方法:


@Override
  public void set(PropertyTokenizer prop, Object value) {
    if (prop.getIndex() != null) {
      Object collection = resolveCollection(prop, object);
      setCollectionValue(prop, collection, value);
    } else {
      setBeanProperty(prop, object, value);
    }
  }
//主要执行的方法
 private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
    try {
      Invoker method = metaClass.getSetInvoker(prop.getName());
      Object[] params = {value};
      try {
        method.invoke(object, params);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (Throwable t) {
      throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
    }
  }


这里面重点就来了,关键就在于metaClass.getSetInvoker(prop.getName()); 中的这个metaClass属性,它其实就是我上面说到的元信息的概念(该理念在流行框架的设计中经常用的),它包含有set方法的信息:


image.png


相关文章
|
12月前
|
Java 数据库连接 数据库
mybatis查询数据,返回的对象少了一个字段
mybatis查询数据,返回的对象少了一个字段
855 9
|
8月前
|
SQL Java 数据库连接
Mybatis拦截器实现公共字段填充
通过使用MyBatis拦截器,可以实现对公共字段的自动填充,简化代码,提高开发效率。拦截器通过拦截SQL操作,在插入和更新操作时自动填充公共字段,使得开发者不再需要手动设置这些字段。本文详细介绍了实现步骤,并通过示例代码展示了具体实现方法,希望能为您的开发工作提供实用的指导和帮助。
504 13
|
10月前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
1620 5
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
11月前
|
SQL Java 数据库连接
mybatis如何仅仅查询某个表的几个字段
【10月更文挑战第19天】mybatis如何仅仅查询某个表的几个字段
438 1
|
Java 数据库连接 数据库
mybatis plus 更新值为null的字段
mybatis plus 更新值为null的字段
190 7
MyBatisPlus如何根据id批量查询?Required request parameter ‘id‘ for method 解决方法是看青戈大佬MybatisPlus的教程
MyBatisPlus如何根据id批量查询?Required request parameter ‘id‘ for method 解决方法是看青戈大佬MybatisPlus的教程
MybatisPlus介绍新增用户,根据id查询,引入MybatisPlus的起步依赖,增删改查最简单的写法
MybatisPlus介绍新增用户,根据id查询,引入MybatisPlus的起步依赖,增删改查最简单的写法
MybatisPlus3---常用注解,驼峰转下滑线作为表明 cteateTime 数据表中的 cteate_time,@TableField,与数据库字段冲突要使用转义字符“`order`“,is
MybatisPlus3---常用注解,驼峰转下滑线作为表明 cteateTime 数据表中的 cteate_time,@TableField,与数据库字段冲突要使用转义字符“`order`“,is
|
3月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
554 1
Spring boot 使用mybatis generator 自动生成代码插件
|
6月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
493 0