这篇文章主要看下mybatis是如何进行结果映射的。
之前提到过,addMapper的过程中,将结果映射的配置保存在了ResultMap中。下面来看下这个类的属性吧。
ResultMap的属性
private Configuration configuration;//全局配置
private String id;//唯一标识
private Class<?> type;//实体类
private List<ResultMapping> resultMappings;//嵌套查询的相关配置
private List<ResultMapping> idResultMappings;
private List<ResultMapping> propertyResultMappings;
private Set<String> mappedColumns;//嵌套查询的参数字段
private Set<String> mappedProperties;//嵌套查询的属性名
private boolean hasNestedResultMaps;//是否含有嵌套的ResultMap
private boolean hasNestedQueries;//是否含有嵌套的子查询
本篇文章,还是基于那个最简单的查询来分析。因此,讲不到嵌套查询。因此ResultMap中就主要用到id,type这两个属性。以下是mapper.ProductMapper的一个抽象方法:
@Select("select * from product where id = #{id}")
Product detail(long id);
这个方法对应的ResultMap的id为"mapper.ProductMapper.detail-long"。type为Product.class。
ResultMap的构建
ResultMap的构建是在addMapper的过程中。代码入口在MapperAnnotationBuilder的parseResultMap方法中。
private String parseResultMap(Method method) {
Class<?> returnType = getReturnType(method);//通过反射获取方法返回类型
ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);//获取ConstructorArgs注解
Results results = method.getAnnotation(Results.class);//获取Results注解,嵌套查询主要是分析这个注解
TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
String resultMapId = generateResultMapName(method);//拼接resultMap的id
applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);//构建ResultMap
return resultMapId;
}
可以看到这个过程主要是通过反射来获取的各个字段。我们上面的例子比较简单,没有注解,所以,只有id和type字段有值。正如前面讲的addMapper之后,这个ResultMap便存在了configuration中的resultMaps了。
查询返回值映射
继续上一篇文章,上一篇文章,讲到查询除了结果,剩下DefaultResultSetHandler将结果按照配置映射好。一下为DefaultResultSetHandler的handleResultSet方法。
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {//为嵌套查询时
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {//没有指定resultHandler则使用默认的
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);//映射数据
multipleResults.add(defaultResultHandler.getResultList());//拼接结果
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
rsw是将jdbc的返回结果包裹了一层,方便处理。resultMap便是这个查询对应的ResultMap,multipleResults则存放最终结果。parentMapping只有在嵌套查询时才会有值。
可以看到我们的案例parentMapping为null,resultHandle也为null。可以看到映射数据走的是handleRowValues方法。
而handleRowValues中又判断了是否含有嵌套查询,当没有嵌套查询时执行handleRowValuesForSimpleResultMap方法。
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();//保留上下文
skipRows(rsw.getResultSet(), rowBounds);//逻辑分页,去掉多余行
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {//遍历数据行
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);//处理Discriminate注解,本文不考虑
Object rowValue = getRowValue(rsw, discriminatedResultMap);//生成映射对象
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());//将映射对象保存在resultHandler中。
}
}
生成映射对象分成了两步,第一步构建映射对象,第二步是为字段赋值。构建映射对象主要用的objectFactory,这个对象保存在configuration中。默认类是DefaultObjectFactory。
对象生成类DefaultObjectFactory
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
if (constructorArgTypes == null || constructorArgs == null) {//当没有传入构造器参数时
constructor = type.getDeclaredConstructor();//获取无参构造器
if (!constructor.isAccessible()) {//保证无参构造器能够访问
constructor.setAccessible(true);
}
return constructor.newInstance();//构建对象并返回
}
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));//获取参数类型对应的构造器
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));//传入参数构建对象并返回
} catch (Exception e) {//错误处理,省略
}
}
DefaultObjectFactory类主要就是通过这个方法构造出对象的。代码就主要是通过反射。
现在生成了结果对象了,但每个字段都是空的,getRowValue中会调用applyAutomaticMappingst方法将查询结果赋值到各个字段中。
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);//将每个字段值和对应的属性生成UnMappedColumnAutoMapping对象,并存到list中。
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {//遍历每个UnMappedColumnAutoMapping
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);//获取值
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);//将对应的属性填充上值
}
}
}
return foundValues;
}
createAutomaticMappings的过程比较繁琐,主要就是遍历结果,将jdbc的字段和对象的属性名对应起来。就不具体看了。最后看看 storeObject是如何将结果保存在resultHandler中的吧。storeObject本质是调用的ResultHandler的handleResult方法。接下来看看DefaultResultHandler类吧。
DefaultResultHandler类
public class DefaultResultHandler implements ResultHandler<Object> {
private final List<Object> list;//保存最终结果
public DefaultResultHandler() {
list = new ArrayList<Object>();
}
@SuppressWarnings("unchecked")
public DefaultResultHandler(ObjectFactory objectFactory) {
list = objectFactory.create(List.class);
}
@Override
public void handleResult(ResultContext<? extends Object> context) {
list.add(context.getResultObject());//将上下文中正在处理的对象添加到list中
}
public List<Object> getResultList() {
return list;
}
}
可以看到默认的ResultHandler非常简单。就是一个list,用add方法将每个结果添加进来。每个结果是通过context.getResultObject()得到的。接下来看下DefaultResultContext类。
public class DefaultResultContext<T> implements ResultContext<T> {
private T resultObject;//最后一个保存的数据
private int resultCount;//保存的结果个数
private boolean stopped;//是否停止
public DefaultResultContext() {
resultObject = null;
resultCount = 0;
stopped = false;
}
@Override
public T getResultObject() {
return resultObject;
}
@Override
public int getResultCount() {
return resultCount;
}
@Override
public boolean isStopped() {
return stopped;
}
public void nextResultObject(T resultObject) {
resultCount++;
this.resultObject = resultObject;
}
@Override
public void stop() {
this.stopped = true;
}
}
可以看到DefaultResultContext非常简单,只是保存了下单个数据,以及处理过的结果个数。
总结
只考虑这种简单查询的话,生成映射结果的流程并不复杂,最核心的两步就是通过反射生成结果对象、填充各个字段的值。
若考虑嵌套查询的话,可以看到ResultMap中保存了子查询的信息在resultMappings等字段中,可以在里面取出子查询的配置,然后执行子查询的方法,子查询的执行过程其实和父查询都是用之前分析的execute.query方法。有一点类似于递归。
若考虑延迟加载的话,mybatis的延迟加载主要使用:Javassist,Cglib实现