“坑”就一个字

震惊: MyBatis使用时: integer返回Long型的坑

2021-12-10  本文已影响0人  码哥说

背景

NEYdb.jpeg

最近基于MyBatis(3.4.5)写了几个通用抽象类用以继承, 为了更通用些, 参数使用了泛型.
大致如下

抽象基类-BaseEntity

@Data
public abstract class BaseEntity<ID> implements Serializable {
    private static final long serialVersionUID = 1L;
    protected ID id;
}

抽象基类-CommonEntity

@Data
public abstract class CommonEntity<ID> extends BaseEntity<ID> {
    private static final long serialVersionUID = 1L;
    //省略其他通用属性
}

抽象基类-DataEntity

@Data
public abstract class DataEntity<ID> extends CommonEntity<ID> {
    private static final long serialVersionUID = 1L;
    //省略其他通用属性
}

然后我们有个表

CREATE TABLE `person` (
  `id` int(11) unsigned NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

对应的实体Person, 继承了以上3个类
Person

@Data
public class Person extends DataEntity<Integer> {
    //private Integer Id; 无需显式声明, 继承自基类
    private String name;
    private Integer age;
}

样使用, 按道理没什么问题, 咸鱼就写了几个查询, 结果一运行

Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: java.lang.Long cannot be cast to java.lang.Integer; nested exception is com.fasterxml.jackson.databind.JsonMappingException: java.lang.Long cannot be cast to java.lang.Integer (through reference chain: java.util.ArrayList[0]->com.mrcoder.sbmannotations.domain.Person["id"])]

总儿言之,Long型无法转为Integer

当时 ,咸鱼就纳闷了, 不敢置信的从数据库定义到实体的属性统统检查了一遍, 从头到尾都没有发现有Long的定义, 那么这个Long从何而来? 又怎么会报这样的错??

带着疑惑, 咸鱼开始了Debug之旅.

究竟哪里开始报的错?

从错误的提示上, 我们根本没法找到哪一步开始错的, 没法子, 断点大法走起.


image.png

断点看出, sql查询出来id值的类型就是Long了.
这就诡异了,根据上面继承结构, Person这个类Id明明应该是Integer类型才对.

难道getPersonById方法有问题?

但我们的getPersonById方法实现很简单,就是直接mybatis执行了查询


image.png

无奈之下, 咸鱼尝试了各种方式(折腾), 发现直接显示在Person类声明

private Integer id;

就不会报错.

难道MyBatis结果集封装时对泛型类型支持有问题??

为了搞清这个问题, 不得不去扒一扒MyBatis的结果集封装实现了.

MyBatis 结果集封装

翻了翻MyBatis源码, 很快找到了


image.png

显然, ResultSetHandler就是专门处理结果集封装的接口类, IDEA跳转实现, 发现DefaultResultSetHandler是它的唯一实现类.

接下来, 我们继续断点大法来验证
直接在handleResultSets断点


image.png

发现跳转到DefaultResultSetHandler.handleResultSets


image.png
继续往下,发现到了DefaultResultSetHandler.getFirstResultSet
image.png
继续到了ResultSetWrapper.ResultSetWrapper(ResultSet rs, Configuration configuration) ,在这个构造方法里我们看到了希望
image.png

循环里就是在对每个字段进行类型、值的填充.
通过断点,我们验证了在这一步就赋值类型错误的事实.


image.png

那么问题来了, 为什么此处赋给id的class是Long?

仔细分析代码

final ResultSetMetaData metaData = rs.getMetaData();

这段获取数据库中的源数据,包含类型、值等信息, 接下来在一个for循环里把源数据进行了处理赋值给实体.
其中,以下这段完成了class的赋值

classNames.add(metaData.getColumnClassName(i));

我们继续断点进入metaData.getColumnClassName(i)方法


image.png

此时f.getMysqlType()拿到了数据库中id的声明类型为“INT UNSIGNED”,所以直接走到了switch的default分支


image.png
也就是f.getMysqlType().getClassName(),此时去MysqlType的枚举中获取className, 发现
image.png

到了这一步, 已经真相大白了!

根本原因

其实, 踩坑的的原因有两点.

或许是Mybatis的“锅”, 又或者是表设计的问题, 总之, 避开以上任意一点, 就不会踩到此坑.

请关注我的订阅号

订阅号.png
上一篇下一篇

猜你喜欢

热点阅读