深入了解MyBatis返回值


深入了解MyBatis返回值

想了解返回值,我們須要了解resultType,resultMap以及接口方法中定義的返回值。

我們先看resultTyperesultMap

resultType和resultMap

大家應該都知道在MyBatis的<select>標簽中有兩種設置返回值的方式,各自是resultMapresultType

處理resultMapresultType的代碼例如以下:

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));
    }
}

這里也轉換為了大寫。

到這里關於resultTyptresultMap就結束了。可是有一個簡單的問題,非常多人不懂,是什么?看下個標題。

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和返回值多少有關系嗎?

沒有不論什么關系。

通過前面resultTyperesultMap的內容,我們應該知道。這個屬性是配置JDBC查詢結果怎樣映射到一個對象上的。

無論返回值是什么或者是幾個,都是依照resultTyperesultMap生成返回結果。

返回結果的類型由resultTyperesultMap決定。

返回結果的類型

返回結果的類型由resultTyperesultMap決定,是不是非常詫異???

實際上就是這樣的情況。。

舉個樣例,有個實體CountryCountry2

接口中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

為什么會這樣呢?

這是由於接口調用方式是對命名空間方式調用的封裝

當你通過命名空間方式調用的時候,返回結果的類型是什么?

就是由resultTyperesultMap決定的類型,這非常easy理解。可是換成接口就覺得不一樣了。

這是由於接口方法方式多了返回值,所以我們會覺得返回的一定是這個類型。

實際上是錯的。

特殊情況

當使用純注解方式時,接口的返回值類型能夠起到作用,假設沒有使用@ResultType注解指定返回值類型。那么就會使用這里寫的返回值類型作為resultType


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM