省流:因为select出来的字段没取别名
一个平静的下午,朋友让我帮他看个问题,前端获取数据列表显示查询成功,但是所有数据都是null
解决过程:
首先考虑通过整个数据的传输链条,跟着数据传播路径逐步缩小范围
- 第一步在后端
controller
打断点,查看前端传入的参数和后端此处的返回是否正常
此处查看发现前端传的查询参数正常,后端返回结果也为null
,因此判定问题出在后端内部 - 走个过场查一下
service
层,与controller
一样;(我好像没见service
单独出过错)因此判断是与数据库交互的部分出现了问题 - 将
mapper.xml
中对应的语句放入MySQL执行,发现没问题,因此问题应该出在Mybatis
的部分 - 仔细考虑
Mybatis
的运行过程,从命名空间到语句id,再到结果封装与映射 - 最后在封装返回结果集处发现问题
大火可以看看自己能不能看出来,很简单的问题,但是也值得深入去了解
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.test.mapper.TestProvinceMapper">
<resultMap id="testProvinceResultMap" type="com.ruoyi.test.domain.TestProvince" autoMapping="true" />
<resultMap id="testProvinceVoResultMap" type="com.ruoyi.test.domain.TestProvinceVo" />
<select id="list" resultMap="testProvinceVoResultMap">
select
tp.province_name,tc.country_name
from
test_province tp
left join
test_country tc
on
tp.country_id = tc.county_id
<where>
<if test="provinceName != null and provinceName != ''">
and province_name = #{provinceName}
</if>
</where>
</select>
</mapper>
以下为本人手写的Mybatis
简单实现对应部分,以此为基础讲一下错误的根源(懒得找Mybatis
自己的)
// 6、封装返回结果集
while(resultSet.next()){
Object o = resultTypeClass.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();
for(int i=1;i<=metaData.getColumnCount();i++){
// 字段名字
String columnName = metaData.getColumnName(i);
// 字段的值
Object value = resultSet.getObject(columnName);
// 使用反射,根据数据库表和实体的对应关系,完成封装
// 根据列名和结果集对象类型构造PropertyDescriptor
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
//获取写入属性的方法,此处的getWriteMethod是根据刚刚传入的columnName进行的
Method writeMethod = propertyDescriptor.getWriteMethod();
//执行写入方法,向o对象中写入value值
writeMethod.invoke(o,value);
}
objects.add(o);
}
Mybatis封装结果集时设置属性值的执行流程
首先是会根据传入字段的名字构造一个PropertyDescriptor
对象
随后根据getWriteMethod()
获取写入对应属性的方法,注意,此处底层是根据刚刚传入的columnName
进行获取write
方法的,具体代码如下:
//第一步使用的构造方法
public PropertyDescriptor(String propertyName, Class<?> beanClass)
throws IntrospectionException {
this(propertyName, beanClass,
Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),
Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));//capitalize会转化为大驼峰
//此处为获取枚举常量值,其实就是'set',然后加上变量名,这也是为什么我们平时一贯要求'set'方法如此命名的原因
}
//上述构造方法调用的另一个构造方法
public PropertyDescriptor(String propertyName, Class<?> beanClass,
String readMethodName, String writeMethodName)
throws IntrospectionException {
·······
setName(propertyName);
setClass0(beanClass);
this.readMethodName = readMethodName;
·······
this.writeMethodName = writeMethodName;
·······
}
//capitalize
public static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
}
可以看出,传入的 columnName
通过内部处理转化为了propertyName
,并且经过转化和拼接构造了writeMethodName
,也就是会根据传入的 columnName
跟set拼接成为为方法名,以同事的错误为例,tp.province_name
字段select出来之后列名会是province_name
,也就是说最后生成的方法名会是setProvince_name
,那么在利用反射寻找方法的时候就会找不到对应的set方法,以至于无法赋值,所以导致最后获取到的元素个数正确但均为null的问题
只能说太久没手写确实很容易犯这种错误,写到这应该算是解释清楚了
但是我又产生新的疑问了,为什么根据错误的方法名去找方法执行,居然不会报错???
等有时间仔细查看一下这中间的执行过程吧