#
问题
在上一文介绍了 JSONObject 接受 MyBatis 的结果集的简单用法,但是在处理一对多的情况时,单纯的JSONObject就不好使了。
比如要查询一个角色下的多个用户,resultMap如下定义
1
2
3
4
5
6
7
8
| <resultMap id="roleMap" type="com.alibaba.fastjson.JSONObject">
<id column="roleId" property="roleId"/>
<result column="roleName" property="roleName"/>
<collection property="users" ofType="com.alibaba.fastjson.JSONObject">
<id column="userId" property="userId"/>
<result column="nickname" property="nickname"/>
</collection>
</resultMap>
|
期望查出来的users
属性对应着一个数组,
然而实际查出来只是一个对象,只有一条数据。
#
解决方案
只需要建一个实体类继承 JSONObject ,里面有你要的集合类型的成员变量,就足够了。
比如我建的 One2Many 类:
1
2
3
| public class One2Many extends JSONObject {
private List<JSONObject> users;
}
|
然后xml改为
1
2
3
4
5
6
7
8
| <resultMap id="roleMap" type="com.heeexy.example.util.model.One2Many">
<id column="roleId" property="roleId"/>
<result column="roleName" property="roleName"/>
<collection property="users" ofType="com.alibaba.fastjson.JSONObject">
<id column="userId" property="userId"/>
<result column="nickname" property="nickname"/>
</collection>
</resultMap>
|
是不是非常简单?
更棒的是,这个 One2Many 类是可以复用的,里面再添加其它的成员变量就 OK 了。而且 Dao 层不需要改动,外面正常的还是用 JSONObject 就可以了。
#
原理
MyBatis 在处理嵌套结果的时候,会判断这个属性的类型,如果是集合,就会初始化一个集合来接收这个属性,否则就只是一个普通的 Object 了。
什么,不满意这个答案?那就拿出源码来吧!
首先我们直接看到最底层判断这个属性是不是集合的这段源码:
DefaultResultSetHandler.instantiateCollectionPropertyIfAppropriate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
final String propertyName = resultMapping.getProperty();
//先去拿在metaObject已经存好的这个属性值,我们这里以users属性为例
Object propertyValue = metaObject.getValue(propertyName);
if (propertyValue == null) {
//在这里拿你的users的java类型,由于我们使用的是JSONObject,这里的类型都会返回Object,如果是One2Many,这里就会拿到List类型
Class<?> type = resultMapping.getJavaType();
if (type == null) {
type = metaObject.getSetterType(propertyName);
}
try {
//判断属性类型是不是集合,如果是结合才会初始化一个集合的值返回到下一步,否则都会返回null
if (objectFactory.isCollection(type)) {
propertyValue = objectFactory.create(type);
metaObject.setValue(propertyName, propertyValue);
return propertyValue;
}
} catch (Exception e) {
throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e);
}
} else if (objectFactory.isCollection(propertyValue.getClass())) {
//propertyValue不为空的情况,即我们的JSONObject里已经塞入了users属性,即使是这样,MyBatis还是要求你塞入的users属性必须是集合,才返回到下一步,否则还是会返回null到下一步。
return propertyValue;
}
return null;
}
|
从上面这段代码我们就知道 MyBatis 确实有做这个判断,你定义的 users 属性到底是不是集合类型,
- 如果是并且没有初始化好的话,就帮你初始化一个集合到下一步,
- 如果已经初始化好了(通常这时候就是已经塞入了几个 user 对象了),就直接返回这个 users 的值到下一步
- 如果不是集合类型,就返回 null 到下一步。
那么下一步到底是干啥呢?正常情况下应该就是继续往 users 集合里添加元素吧。
DefaultResultSetHandler.linkObjects
1
2
3
4
5
6
7
8
9
10
11
| private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
if (collectionProperty != null) {
//如果上一步返回来的不是null,那就向这个集合里添加元素
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
targetMetaObject.add(rowValue);
} else {
//上一步返回了null,那就直接把这个属性赋上本值,就是这种情况导致了上面的第二张图片的情况,我们的users变成了一个对象,而不是想要的数组。
metaObject.setValue(resultMapping.getProperty(), rowValue);
}
}
|