使用mybatis查询,并映射为javaBean时。数据库直接查询,该属性字段有值,但每次属性的set方法被调用时入参都是空,然而在mybatis最终的返回中,javaBean的属性又全都出现了值。这等诡异事件是如何发生的呢?
场景需求:将sql查询结果映射到javaBean中的一个List属性,同时通过该属性set方法,在里面做一些操作。
现象:mybatis查询后,调用了List roles属性的set方法,但是入参是空数组,但在对象构建完成后,roles却又出现了2个值。
set方法调用情况:
mybatis返回的对象结果:
依赖如下:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
猜想:
1.mybatis调用set方法注入的是一个空数组,然后将地址给实体属性,再往注入的数组中加入值,从而达到目的。
2.mybatis查询结果返回的对象在创建后,返回前被修改过,比如反射。
验证
方案一:
将属性set方法重写为:
重新请求,现象完全一致,因此应该不是方案1导致。
方案二验证
查询处打一个断点,跟着代码走,
发现Sql执行完成后会走到一个BeanWrapper --> setBeanProperty方法
解释说明:object就是创建的bean,value是值,prop是属性相关的东西,大约长下图这样。
此时可用看到value确实也是一个空数组,根据之前的信息,其有值的情况是发生在这之后的,所以继续往后走。
到达 DefaultResultSetHandler -->applyNestedResultMappings方法时,发现此时roles属性仍旧是一个空数组
但是此时也发现了我们所需要的数据
因为知道数组大小是2,因此放过一次,验证是否是这里将值放进去了。
果然,此时roles数组中已经放入了一个值,所以便继续进入DefaultResultSetHandler -->linkObjects查看是如何放入数据的。此时关闭其他地方所有断点,仅留下此一处。
instantiateCollectionPropertyIfAppropriate目的是初始化参数, 并未改变值。roles这个属性值发生改变(数组中增加数据)的时候是targetMetaObject.add(rowValue);这个方法调用后。
查资料发现,MetaObject是一个强大的反射工具类,支持查找属性(忽略大小写、支持驼峰、支持子属性)、查找子属性(“user.name”、"users[0].id"获取集合中子属性的值、"user[name]"获取map)、支持设置子属性。
也就说猜想2也不全对,初始化前后调用的是同一个类,只是加入值时是调用的list的add方法。那我们就进一步验证.
// 1.重写List的Add方法
public class MyList<E> extends ArrayList {
@Override
public boolean add(Object o) {
System.out.println("add方法被调用了:" + JSONObject.toJSONString(o));
return super.add(o);
}
}
//2.属性类型重写
private MyList<String> roles;
// 3.set方法重写为
public void setRoles(MyList<String> roles) {
System.out.println("入参:" + JSONObject.toJSONString(roles));
this.roles = roles;
}
重新启动服务验证,日志打印结果:
总结:mybatis在映射查询结果为一个javaBean时,对于List类型,是先为其进行赋值(空数组,查询无结果的情况此文不论),然后通过MetaObject为其子类设置值,设置值时调用的是其子类方法(此处是List的add方法)。因此在javaBean中,对于List类型的数据,其set方法获得的是一个空数组,而值是在后续通过add方法的调用获得的。
因此,通过mybatis查询映射的List属性,并不能在该属性set方法中对其值做出处理。