深入了解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
方式
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
方式
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
和傳回值多少有關系嗎?
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
。