看看我们关心的id属性:
oh my god。元数据里面保存的根本就不是我们以为的setId(Integer id)这种,而是保留有父类自己的东西。所以我们自然就好理解了,为什么set进去一个BigInteger值竟然也不抱错的原因了(它也继承了Number类)。
到此,我们就算把出现这种现象的原因完全给弄明白了。
but,but,but。这其实并没有彻底的让我“心服口服”,至少有两大问题一直困扰着我,没有找到根本原因。
疑问提出
(此处暂时只提出两个问题做出解答,更加详细的,可以关注后续我的撸管MyBatis源码专题)
1、为何getValue匹配类型转换器的时候,找到的是UnknownTypeHandler?
(本问题此处大概讲一下,更加详细的,MyBatis的类型转换器模式,完全需要拉一个专题出来讲解)
MyBatis内部注册和维护了几乎所有的类型转换器,所以我们平时使用的时候根本就不用管,它自动就能跟我们匹配上,转换成我们需要的结果。在初始化的时候,有个转换器注册类:TypeHandlerRegistry:(列出部分)
我们会发现3.4版本的MyBatis对 JSR 310标准的日期时间也提供了支持
顺带我们可以看一下,框架升级给我们带来的优雅体验:
我们发现3.4.6版本处理起来,就优雅很多,大赞。
UnknownTypeHandler对应的类型:
register(Object.class, UNKNOWN_TYPE_HANDLER); register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
我们会发现,它对应的都是Object类型。
MyBatis在进行初始化的时候,会把所有的xml文件里的ResultMap进行注册,并且提供全局访问。而当注册到此处的继承情况的时候,在获取xml继承的id类型的时候,因为是继承的,所以拿不到实际类型,从而注册不到对应的处理器,最终只能交给UnknownTypeHandler处理
下面一个简单的例子,大家可以感受一下MyBatis为啥注册时候找不到了:
public static void main(String[] args) throws NoSuchFieldException { //Field id = Son.class.getField("id"); //System.out.println(id); //若id是从父类继承来的,传入的泛型 java.lang.Number com.sayabc.boot2demo1.api.TestController$BaseEntity.id //System.out.println(id); //id是本类自己的属性 public java.lang.Integer com.sayabc.boot2demo1.api.TestController$Son.id //另外一种方式(属性都是private的都ok) Field id = Son.class.getSuperclass().getDeclaredField("id"); System.out.println(id); //我们会发现获取的SuperClass的 类型直接是java.lang.Number 根本没得商量 } class Son extends BaseEntity<Integer> { //public Integer id; private String name; } //此处为了方便反射 属性用public的 class BaseEntity<PK extends Number> { public PK id; }
我们能够得出结论。当属性是从父类继承过来的,反射去获取这个字段的类型,它的类型是父类类型。(本例如果没有继承自Number,那返回的就是Object类型)
2、为何刚看到的元数据metaClass对象保存的是父类的setId方法呢?作何考虑?这个值又是什么时候被赋值放进去的呢?
这几个问题其实相对来说比较简单些,如果熟悉流行开源框架的这方面的设计思想,发现都是通的,大家都这么“玩”。因此这个问题我这里就不做解答了,留给读者自己思考一番吧
MyBatis结果集如果是Map遇上泛型的话,也是可能遇上同样问题的。
说到最后
框架能极大提高我们的开发效率,甚至我们可以基于开源本身定制出更符合我们业务的框架。
一件事本身的复杂度不会减少,它只是从一个地方转移到了另外一个地方而已,总的复杂度是恒定不变的,这是一个定理。
(比如这次的撸源码调试找问题就非常耗时,从开始到搞明白花了整4个小时左右,耗时的原因关键是MyBatis自己存在我上面指出的软病,加大了定位问题的难度)