您的位置:首页 > 其它

深入了解MyBatis返回值

2015-06-10 17:07 363 查看

深入了解MyBatis返回值

想了解返回值,我们需要了解
resultType
,
resultMap
以及接口方法中定义的返回值。

我们先看
resultType
resultMap


resultType和resultMap

大家应该都知道在MyBatis的
<select>
标签中有两种设置返回值的方式,分别是
resultMap
resultType


处理
resultMap
resultType
的代码如下:

private void setStatementResultMap(
        String resultMap,
        Class<?> resultType,
        ResultSetType resultSetType,
        MappedStatement.Builder statementBuilder) {
    resultMap = applyCurrentNamespace(resultMap, true);

    List<ResultMap> resultMaps = new ArrayList<ResultMap>();
    if (resultMap != null) {
        String[] resultMapNames = resultMap.split(",");
        for (String resultMapName : resultMapNames) {
            try {
                resultMaps.add(configuration.getResultMap(resultMapName.trim()));
            } catch (IllegalArgumentException e) {
                throw new IncompleteElementException("Could not find result map " + resultMapName, e);
            }
        }
    } else if (resultType != null) {
        ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
                configuration,
                statementBuilder.id() + "-Inline",
                resultType,
                new ArrayList<ResultMapping>(),
                null);
        resultMaps.add(inlineResultMapBuilder.build());
    }
    statementBuilder.resultMaps(resultMaps);

    statementBuilder.resultSetType(resultSetType);
}


可以看到这里会优先处理
resultMap
但是也使用了
resultType


接下来看MyBatis获取数据后,如果处理一行结果(以简单数据为例,不考虑嵌套情况):

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
    if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(resultObject);
        boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
        if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
        }
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        resultObject = foundValues ? resultObject : null;
        return resultObject;
    }
    return resultObject;
}


上面这段代码中重要的代码如下:

if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
    foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;


if中判断的是当前是否支持自动映射(可以配置),这一点很重要,如果不支持,那么没法使用
resultType
方式,必须用
resultMap
方式,如果支持
resultType
方式和
resultMap
方式可以同时使用

这里的基本逻辑是先对没有
resultMap
的属性自动映射赋值,通过
applyAutomaticMappings
实现。

如果对象有
resultMap
,那么还会进行
applyPropertyMappings
方法。

也就是先处理
resultType
中自动映射的字段,在处理
resultMap
中的配置的字段,两者可以同时使用!

下面按照顺序分别说两种方式。

resultType
方式

如果支持自动映射,那么会执行
applyAutomaticMappings
,这里面有
metaObject
参数。

final MetaObject metaObject = configuration.newMetaObject(resultObject);


我们看看创建
metaObject
最关键的一个地方,在
Reflector
类中:

for (String propName : readablePropertyNames) {
    caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}


这里将实体中的属性名,做了一个映射,是大写的对应实际的属性名。例如
ID:id


applyAutomaticMappings
中的第一行,首先获取没有映射的列名:

final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);


获取列名的时候:

for (String columnName : columnNames) {
    final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
    if (mappedColumns.contains(upperColumnName)) {
        mappedColumnNames.add(upperColumnName);
    } else {
        unmappedColumnNames.add(columnName);
    }
}


注意这里将列名转换为大写形式,同时保存了
mappedColumnNames
映射的列和
unmappedColumnNames
未映射的列。

因为不管是属性名还是查询列都是大写的,所以只要列名和属性名大写一致,就会匹配上。

因此我们在写sql的时候,不需要对查询列的大小写进行转换,自动匹配是不区分大小写的。

resultMap
方式

这种方式也很简单,上面提到了
mappedColumnNames
,在判断是否为映射列的时候,使用
mappedColumns.contains(upperColumnName)
进行判断,
mappedColumns
是我们配置的映射的列,那是不是我们配置的时候必须大写呢?

实际上不用,这里也不区分大小写,在
<result column="xxx" ../>
column
也不区分大小写,看下面的代码:

for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
    final String compositeColumn = compositeResultMapping.getColumn();
    if (compositeColumn != null) {
        resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
    }
}


这里也转换为了大写。

到这里关于
resultTypt
resultMap
就结束了,但是有一个简单的问题,很多人不懂,是什么?看下个标题。

MyBatis接口返回值

接口返回值通常是一个结果,或者是
List
和数组。

MyBatis如何知道我想要返回一个结果还是多个结果?

MapperMethod
中的部分代码如下:

if (method.returnsVoid() && method.hasResultHandler()) {
    executeWithResultHandler(sqlSession, args);
    result = null;
} else if (method.returnsMany()) {
    result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
    result = executeForMap(sqlSession, args);
} else {
    Object param = method.convertArgsToSqlCommandParam(args);
    result = sqlSession.selectOne(command.getName(), param);
}


可以看到查询结果有4中情况,
void
,
list
(和
array
),
map
,
one


这里重要就是if的判断条件,这种判断条件计算方法:

this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());


可以看到,这些条件完全就是通过方法的返回值决定的。所以如果你写的返回值是数组或者集合,返回的结果就是多个。

如果返回值本身有多个,但是返回值写了一个POJO,不是集合或者数组时会怎样?

答案是会报错
TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size())


不管是返回一个结果还是多个结果,MyBatis都是安装多个结果进行查询,
selectOne
是查询一个,
selectList
是查询多个,我们看看
selectOne
代码:

public <T> T selectOne(String statement, Object parameter) {
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}


注意看:

List<T> list = this.<T>selectList(statement, parameter);


实际上,不管查询一个还是多个结果,MyBatis都是先按多个结果进行查询。拿到
list
结果后在判断。

如果是查询一个结果的情况,那么
list
最多只能有一个返回值。通过上面代码的
if else if esle
可以很明白的理解。

resultTyp
,
resultMap
和返回值多少有关系吗?

没有任何关系。

通过前面
resultType
resultMap
的内容,我们应该知道,这个属性是配置JDBC查询结果如何映射到一个对象上的。

不管返回值是什么或者是几个,都是按照
resultType
resultMap
生成返回结果。

返回结果的类型由
resultType
resultMap
决定。

返回结果的类型

返回结果的类型由
resultType
resultMap
决定,是不是很诧异???

实际上就是这种情况。。

举个例子,有个实体
Country
Country2


接口中
List<Country> selectAll()
,xml中的
<select id="selectAll" resultType="Country2">
.

当你通过接口调用的时候,返回值是什么?你以为自己的
List
中的对象类型是
Country
,但他们实际上都是
Country2


如果接口方法为
Country selectById(Integer id)
,xml中为
<select id="selectById" resultType="Country2">
,由于类型不一致,查询的时候才会报错:
java.lang.ClassCastException: xx.Country2 cannot be cast to xx.Country


为什么会这样呢?

这是因为接口调用方式是对命名空间方式调用的封装

当你通过命名空间方式调用的时候,返回结果的类型是什么?

就是由
resultType
resultMap
决定的类型,这很容易理解。但是换成接口就觉得不一样了。

这是由于接口方法方式多了返回值,所以我们会认为返回的一定是这个类型。实际上是错的。

特殊情况

当使用纯注解方式时,接口的返回值类型可以起到作用,如果没有使用
@ResultType
注解指定返回值类型,那么就会使用这里写的返回值类型作为
resultType
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: