深入了解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
決定的類型,這非常easy理解。可是換成接口就覺得不一樣了。
這是由於接口方法方式多了返回值,所以我們會覺得返回的一定是這個類型。
實際上是錯的。
特殊情況
當使用純注解方式時,接口的返回值類型能夠起到作用,假設沒有使用@ResultType
注解指定返回值類型。那么就會使用這里寫的返回值類型作為resultType
。