天天看點

深入了解MyBatis傳回值深入了解MyBatis傳回值

深入了解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() > ;
        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() >  || 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() == ) {
        return list.get();
    } else if (list.size() > ) {
        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