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

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

     为了追查此问题根源,本人通过复现现象、控制变量、调试源码等方式,苦心全身心投入连续查找近4个小时,终于找出端倪。现通过本文分享给大家,希望对各位有所帮助。


问题背景


     为了简化持久层的开发,减少或杜绝重复SQL的书写,提高开发效率和减少维护成本,本人基于MyBatis写了一个操作DB的中间件。为了规范操作,中间件提供了一个带泛型化参数的抽象类供以继承(BaseDBEntity),利用泛型的模版特性,来实现统一控制(包括统一查询、统一分页处理等等)。BaseDBEntity部分源码:


///

public abstract class BaseDBEntity<T extends BaseDBEntity<T, PK>, PK extends Number> {
  private PK id;
  ... //省略get、set方法
}


贴上我们问题模块Entity的继承情况:


public class SupplementDomain extends BaseDBEntity<SupplementDomain, Integer> {
  private Long teacherId;
    private String operateNo;
    ... //省略其余属性和get/set方法
}


但是查询后,情况如下:


image.png


我从结果集里就能看出来,id现在是一个BigInteger类型的值。这就诡异了,根据上面的的代码继承结构,SupplementDomain这个类明明应该是Integer类型才对(备注:此问题我咋一看其实并不陌生,因为SpringMVC也有类似的Bug存在,这“得益于”Java的泛型的根本问题,有点无解。参考博文:【小家java】为什么说Java中的泛型是“假”的?(通过反射绕过Java泛型))。

因为存在这样的直接原因,导致我们哪怕只执行简单的


Integer id = bean.getId(); //类型转换异常


都会报错。只要不操作它,才相安无事。因此具有极大的安全隐患,虽然我已告知使用的同事处理的办法,但是并没有知道其根本原因,心里着实不踏实。因此才有了本文,无奈只能撸源码,看看MyBatis到底是怎么样把这给封装错了的。


源码分析


偌大的MyBatis源码,从哪下手呢?我首先摆出了它的四大核心组件:


ParameterHandler 、ResultSetHandler 、StatementHandler 、Executor


很显然,根据我对MyBatis的了解,ResultSetHandler首当其冲。跟着源码一层一层探讨一下MyBatis把数据库记录集映射到POJO对象的一个简要的过程。

根据之前有大概看过几大核心对象的源码,所以我知道ResultSetHandler只有一个一个实现类:DefaultResultSetHandler,所以没什么好说的,进去看吧,封装结果集的入口方法:


@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException { }

Tip:从解析结果集里面可以看出,MyBatis是先new出来了一个List multipleResults,是遵循尽量少的null元素的设计的。所以Dao层查出来的List,以后都不用判断Null,清晰了代码结构


内部核心,其实是循环调用了handleResultSet方法,所以主要跟踪这个方法:


private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }


handleRowValues方法把处理后的结果列表添加到List内(multipleResults内),因此其实我们可以得出一个初步结论:不管方法handleRowValues里面调用的层次多深,最终把结果集ResultSet经过处理,得到了需要的那些POJO对象并存储到一个List里面。所以我们重点看看handleRowValues方法,先看断点后的几张数据截图:


image.png


image.png


从图中可以看到,此处Mybatis已经把一些元信息(包括Java类字段、数据库字段、映射关系、处理器等)都已经准备好了,接下类就是用这个方法去封装一行数据到一个java的POJO。


 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }


方法中分两种情况分别调用了两个方法,前一种是resultMap中有嵌套(MyBatis支持嵌套子查询Select),后一种没有嵌套,这里重点看看后一种方法:


  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    skipRows(rsw.getResultSet(), rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }


简单一浏览就能看到,这里最重要的方法,就是getRowValue:


 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(resultObject);
      boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      resultObject = foundValues ? resultObject : null;
      return resultObject;
    }
    return resultObject;
  }


这个方法需要好好读一下,它做的事是把一行数据封装成一个Java对象,所以第一步可以看到它调用了createResultObject方法创建了一个对象,方法内部较为复杂,但我们简单理解为它就是通过反射给我newInstance了一个空对象:


image.png


备注lazyLoader表示的是否要延迟加载,这是MyBatis的一个特性:支持懒加载。我们默认都是实时加载的


相关文章
|
2月前
|
Java 数据库连接 数据库
mybatis查询数据,返回的对象少了一个字段
mybatis查询数据,返回的对象少了一个字段
187 8
|
2月前
|
SQL XML Java
mybatis-源码深入分析(一)
mybatis-源码深入分析(一)
|
9天前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
29天前
|
SQL Java 数据库连接
mybatis如何仅仅查询某个表的几个字段
【10月更文挑战第19天】mybatis如何仅仅查询某个表的几个字段
34 1
|
1月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
37 10
|
1月前
|
前端开发 Java 数据库连接
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
本文是一份全面的表白墙/留言墙项目教程,使用SpringBoot + MyBatis技术栈和MySQL数据库开发,涵盖了项目前后端开发、数据库配置、代码实现和运行的详细步骤。
50 0
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
1月前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
133 0
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
|
1月前
|
SQL Java 数据库连接
Mybatis中传入不同类型的值处理方案
这篇文章讲述了在Mybatis中如何处理传入不同类型参数的情况,包括单个值、列表及Map等,并提供了相应的XML映射和Java代码示例。
79 0
|
3月前
|
XML Java 数据库连接
mybatis源码研究、搭建mybatis源码运行的环境
这篇文章详细介绍了如何搭建MyBatis源码运行的环境,包括创建Maven项目、导入源码、添加代码、Debug运行研究源码,并提供了解决常见问题的方法和链接到搭建好的环境。
mybatis源码研究、搭建mybatis源码运行的环境
|
3月前
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。