前言
考慮到在Select時使用AS和方案一其實沒什麼差別,在介紹ResultMap之前,順便帶過一下。
方案二-Select … AS
當我們的數據庫列名和對象字段之間不是駝峯式命名的關係,我們可以在Select時使用AS,使得列名和對象名匹配上。
映射文件中是本次會執行的sql,我們會查出id,city_id,city_name,city_en_name。 按照開啓的駝峯式命名開關,我們會對應到對象的id,cityId,cityName,cityEnName字段。
<select id="selectCity" resultType="po.CityPO">
select id,city_id,city_name,city_en_name from SU_City where id = #{id}
</select>
不過在這次,我們對PO做了小小的改動,把cityEnName改成了cityEnglishName。
public class CityPO {
Integer id;
Long cityId;
String cityName;
String cityEnglishName; // 由cityEnName改成了cityEnglishName
由於找不到匹配的列,cityEnlishName肯定沒法被反射賦值,要爲Null了。
CityPO{id=2, cityId=2, cityName='北京', cityEnglishName='null'}
**解決辦法: **在Select字段的時候使用AS,下面是改動後的映射文件。
<select id="selectCity" resultType="po.CityPO">
select id,
city_id,
city_name,
city_en_name AS cityEnglishName
from SU_City
where id = #{id}
</select>
改動後執行得到的結果如下。
CityPO{id=2, cityId=2, cityName='北京', cityEnglishName='beijing'}
那麼我們來看看它是如何生效的,主要的代碼在哪裏。在昨天我們第一個介紹的函數handleRowValues中傳入了參數rsw,它是對ResultSet的一個包裝,在這個包裝裏,完成了具體使用哪個名字作爲數據庫的列名。
final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
在這個構造函數當中,我們會獲取數據庫的列名,AS爲什麼可以生效,具體就在下面這段代碼。
super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
// 在這裏
columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
classNames.add(metaData.getColumnClassName(i));
}
在添加列名時,會從配置中獲取是否使用類標籤,isUseColumnLabel,默認爲true。根據Javadoc,這個ColumnLabel就是AS後的那個名字,如果沒有AS的話,就是獲取的原生的字段名。
/**
* Gets the designated column's suggested title for use in printouts and
* displays. The suggested title is usually specified by the SQL <code>AS</code>
* clause. If a SQL <code>AS</code> is not specified, the value returned from
* <code>getColumnLabel</code> will be the same as the value returned by the
* <code>getColumnName</code> method.
*
* @param column the first column is 1, the second is 2, ...
* @return the suggested column title
* @exception SQLException if a database access error occurs
*/
String getColumnLabel(int column) throws SQLException;
後面的過程就和昨天講的方案一一模一樣了,不再贅述。
方案三-ResultMap
resultMap 元素是 MyBatis 中最重要最強大的元素。它就是讓你遠離 90%的需要從結果 集中取出數據的 JDBC 代碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支持的事 情。 事實上, 編寫相似於對複雜語句聯合映射這些等同的代碼, 也許可以跨過上千行的代碼。 ResultMap 的設計就是簡單語句不需要明確的結果映射,而很多複雜語句確實需要描述它們 的關係。
ResultMap是Mybatis中可以完成複雜語句映射的東西,但在我們的日常開發中,我們往往是一個XML對應JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 對象),並沒有特別複雜的應用,下面也是基於日常的使用,看看簡單的ResultMap在源碼層面是如何展現的。
<resultMap id="cityMap" type="po.CityPO">
<result column="id" property="id"/>
<result column="city_id" property="cityId"/>
<result column="city_name" property="cityName"/>
<result column="city_en_name" property="cityEnglishName"/>
</resultMap>
<select id="selectCity" resultMap="cityMap">
select id,
city_id,
city_name,
city_en_name
from SU_City
where id = #{id}
</select>
在resultMap的子元素result對應了result和對象字段之間的映射,並通過id標示,你在Select語句中指定需要使用的resultMap即可。
源碼層面的話,依舊在DefaultResultSetHandler的handleResultSets中處理返回集合。
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
在這次的ResultMap中,相比之前方案,其屬性更加的豐富起來。將之前寫的Result的信息保存在了resultMappings,idResultMappings等中,以備後續使用。
後續的函數走向和方案一二一致,在創建自動映射的時候出現了不同。
privateList<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)throwsSQLException {
在這個函數中,會獲取沒有映射過的列名。
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
之後會根據resultMap查看是否有未映射的字段。
loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
List<String> mappedColumnNames = new ArrayList<String>();
List<String> unmappedColumnNames = new ArrayList<String>();
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
// 這裏沒有配置前綴,根據之前的圖,定義了ResultMap後,會記錄這些已經配置映射的字段。
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
for (String columnName : columnNames) {
// 遍歷列名,如果在已映射的配置中,那麼就加入已經映射的列名數據,
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
// 生成未映射和已映射的Map
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
如果有沒配置在ResultMap中,且Select出來的,那麼之後也會按照之前方案一那樣,繼續往下走,去對象中尋找映射關係。
由於沒有未映射的字段,使用自動映射的結果是false。
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
之後繼續往下走,使用applyPropertyMappings來創建對象。使用了PropertyMapping。裏面包含了字段名,列名,字段的類型和對應的處理器。
遍歷整個Mappings。
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
函數裏主要的就是獲取這個字段對應的類型處理器,防止類型轉換失敗,這一部分下次會專門看一下。
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
TypeHandler就是一個接口,主要完成的工作就是從Result根據列名,獲取相應類型的值,爲下一步反射賦值做準備。至於它是怎麼決定爲什麼用這個類型的TypeHandler下次再看。最後就是給對應字段賦值。
metaObject.setValue(property, value);
最後就完成了整個類的賦值。
總結
大致上,Mybatis完成映射主要是兩種方式。
-
只根據列名,利用自動映射,根據反射類的信息,得到列名和字段之間的關係,使用對應的TypeHandler,完成字段的賦值。
-
使用ResultMap預先定義好映射關係,也是最後根據TypeHandler和反射,完成字段的賦值。
我個人感覺就簡單的用法來說,兩者都可以,在一次會話中,Configuration中的ResultMap關係建立好,在每一次查詢的時候就不用再去重新建立了,直接用就行。而自動映射的話,執行過一次後,也會在會話中建立自動映射的緩存。所以沒什麼差別。但如果複雜的映射的話,就非ResultMap莫屬啦。具體可以參考Mybatis文檔關於映射的章節,因爲目前用不到比較複雜的映射, 不做深究了。
http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html