目录
一.问题描述
二.源码分析
三.总结
一.问题描述
事情的起因是用MybaitsPlus查询数据库过程中,查询结果与要封装的实体类字段类型对应不上,类似这样:
数据库查询结果:
java实体类:
@TableName("my_user")@ToStringpublic class User { private Long id; private String name; private BigDecimal age; public User(String name,BigDecimal age){ this.name=name; this.age=age; }}
字段名字和实体类的名字类型都能对应上,但最后的查询结果却会报错java.sql.SQLDataException: Cannot determine value type from string
意思就是String类型的‘zhang’没有对应的类型实体类字段,这就很奇怪了,经过排查,是因为加了有参构造器,有参构造器去掉或者加上注解@NoArgConstructor就可以解决问题,不想看原因的可以直接跳到后面看结论
二.源码分析
根据报错信息找到了Mybatis中DefaultResultSetHandler这个类的createResultObject方法,这是创建返回的实体类的方法
这里我们看到已经拿到了实体类的Class对象,并且走到了createByConstructorSignature方法,这个方法顾名思义就是通过构造器来创建对象,我们再进这个方法看看
这个方法会拿到一个defaultConstructor默认构造器,那这个defaultConstructor又是从哪里来的呢,点进去findDefaultConstructor方法看一下
这个方法里也很简单,如果只有一个构造器就返回这个构造器,User类只有一个构造器,返回的就是(String,Bigdecimal)的构造器,回到createByConstructorSignature这个方法中,接下来就会进入createUsingConstructor这个方法中
这个方法会通过for循环把构造器中的参数和查询出的结果一一对应,并添加到constructorArgs中,最后通过objectFactory.create(resultType, constructorArgTypes, constructorArgs)这个方法来用这个构造器反射生成实力对象。可以看到第一个参数就出问题了,构造器中第一个参数是String类型,而查询出来的第一个字段id应该是Long类型的,显然对应不上
查询出来的id为String类型的1234,而到第二个参数就会变成BigDecimal类型去对应String类型的name,这无法转换就会报错
那如果去掉有参的构造器呢,去掉之后实体类里就只会剩下空参的构造器,再执行到createResultObject方法时,我们就可以看到执行到metaType.hasDefaultConstructor()这个方法时返回的是true,而defaultConstructor就是空参构造器,所以进入的是objectFactory.create(resultType)这个方法,这个方法里会通过这个空参的构造器来反射生成一个对象,后续再通过反射给这个对象赋值
三.总结
在mybatis中,用于承接sql执行结果的实体类最好要有一个空参构造器,否则很可能出现类型对应不上的问题,因为Mybaits会优先用空参构造器来反射生成对象,如果没有空参构造器则会用有参的构造器,这时候查询的返回结果必须和构造器的参数一一对应,而且因为是Mybatis框架的原因,所有数据库都有可能出现这个情况,只是抛出的提示会有所不同,值得注意的是如果用了@Bulider这个注解编译后会生成一个全参的构造器,需要注意