【方向盘】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的一个特性:支持懒加载。我们默认都是实时加载的


相关文章
|
1月前
|
JSON JavaScript 前端开发
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
42 0
|
1月前
|
编译器 C语言
c语言中long的作用类型
c语言中long的作用类型
29 0
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——实体层(User.java)
mybatis简单案例源码详细【注释全面】——实体层(User.java)
13 0
|
15天前
|
SQL Java 数据库连接
深入源码:解密MyBatis数据源设计的精妙机制
深入源码:解密MyBatis数据源设计的精妙机制
28 1
深入源码:解密MyBatis数据源设计的精妙机制
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)
mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)
13 0
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——测试层(UserMapperTest.java)
mybatis简单案例源码详细【注释全面】——测试层(UserMapperTest.java)
10 0
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Dao层映射文件(UserMapper.xml)【重要】
mybatis简单案例源码详细【注释全面】——Dao层映射文件(UserMapper.xml)【重要】
10 0
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Dao层接口(UserMapper.java)
mybatis简单案例源码详细【注释全面】——Dao层接口(UserMapper.java)
7 0
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——实体层(Role.java)
mybatis简单案例源码详细【注释全面】——实体层(Role.java)
7 0
|
1月前
|
Java 关系型数据库 数据库连接
mybatis简单案例源码详细【注释全面】——前期准备
mybatis简单案例源码详细【注释全面】——前期准备
11 0