上文Mybatis之方法如何映射到XML中介紹了Mybatis是如何將方法進行分拆出方法名映射到statementID,參數如何解析成xml中sql所須要的,以及返回類型的處理;本文將從XML端來看是如何同方法端進行映射的。html
前兩篇文章中瞭解到經過Mapper類路徑+方法名映射xxMapper.xml中的namespace+statementID,而namespace+statementID塊其實在初始化的時候在Configuration中保存在MappedStatement中,因此咱們在增刪改查的時候都會看到以下代碼:java
MappedStatement ms = configuration.getMappedStatement(statement);
在Configuration中獲取指定namespace+statementID的MappedStatement,而在Configuration是經過Map維護了對應關係;已最多見的Select語句塊爲例,在XML中的配置的結構以下:node
<select id="selectPerson" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY"> ...sql語句... </select>
其餘增刪改除了個別的幾個關鍵字好比:keyProperty,keyColumn等,其餘和select標籤相似;再來看一下MappedStatement類中相關的屬性:git
public final class MappedStatement { private String resource; private Configuration configuration; private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets; ...省略... }
select標籤裏面的關鍵字基本能夠在類MappedStatement中找到對應的屬性,關於每一個屬性表明的含義能夠參考官方文檔:mybatis-3;除了關鍵字還有sql語句,對應的是MappedStatement中的SqlSource,sql語句有動態和靜態的區別,對應的SqlSource也提供了相關的子類:StaticSqlSource和DynamicSqlSource,相關的sql解析類在XMLScriptBuilder中:github
public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
具體哪一種sql是動態的,哪一種是靜態的,相關邏輯在parseDynamicTags中判斷的,此處大體說一下其中的原理:遇到${}和動態標籤如<if>,<set>,<foreach>則爲DynamicSqlSource,不然爲StaticSqlSource也就是常見的#{};在解析動態sql的時候Mybatis爲每一個標籤專門提供了處理類NodeHandler,初始化信息以下:sql
private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); }
處理完以後會生成對應的SqlNode以下圖所示:
數據庫
無論是動態仍是靜態的SqlSource,最終都是爲了獲取BoundSql,如SqlSource接口中定義的:express
public interface SqlSource { BoundSql getBoundSql(Object parameterObject); }
這裏的parameterObject就是上文中經過方法參數生成的sql參數對象,這樣BoundSql包含了sql語句,客戶端傳來的參數,以及XML中配置的參數,直接能夠進行映射處理;segmentfault
上節中將到了BoundSql,本節重點來介紹一下,首先能夠看一下其相關屬性:緩存
public class BoundSql { private final String sql; private final List<ParameterMapping> parameterMappings; private final Object parameterObject; private final Map<String, Object> additionalParameters; private final MetaObject metaParameters; ...省略... }
幾個屬性大體含義:要執行的sql語句,xml配置的參數映射,客戶端傳來的參數以及額外參數;已一個常見的查詢爲例能夠看一下大體的內容:
<select id="selectBlog3" parameterType="hashmap" resultType="blog"> select * from blog where id = #{id} and author=#{author,jdbcType=VARCHAR,javaType=string} </select>
此時sql對應就是:
select * from blog where id = ? and author=?
parameterMappings對應的是:
ParameterMapping{property='id', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'} ParameterMapping{property='author', mode=IN, javaType=class java.lang.String, jdbcType=VARCHAR, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
parameterObject對應的是:
{author=zhaohui, id=158, param1=158, param2=zhaohui}
若是知道以上參數,咱們就能夠直接使用原生的PreparedStatement來操做數據庫了:
PreparedStatement prestmt = conn.prepareStatement("select * from blog where id = ? and author=?"); prestmt.setLong(1,id); prestmt.setString(2,author);
其實Mybatis本質上和上面的語句沒有區別,能夠看一下Mybatis是如何處理參數的,具體實如今DefaultParameterHandler中,以下所示:
public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }
大體就是遍歷parameterMappings,而後經過propertyName到客戶端參數parameterObject中獲取對應的值,獲取到值以後就面臨一個問題一個就是客戶端參數的類型,另外一個就是xml配置的類型,如何進行轉換,Mybatis提供了TypeHandler來進行轉換,這是一個接口類,其實現包括了經常使用的基本類型,Map,對象,時間等;具體使用哪一種類型的TypeHandler,根據咱們在xml中配置的<javaType=類型>來決定,若是沒有配置則使用UnknownTypeHandler,UnknownTypeHandler內部會根據value的類型來決定使用具體的TypeHandler;Mybatis內部全部的類型都註冊在TypeHandlerRegistry中,因此獲取的時候直接根據value類型直接去TypeHandlerRegistry獲取便可;獲取以後直接調用typeHandler.setParameter(ps, i + 1, value, jdbcType),已StringTypeHandler爲例,能夠看一下具體實現:
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); }
使用了原生的PreparedStatement,往指定位置設置參數值;設置完參數以後就執行execute方法,返回結果;
上一節執行execute以後,返回的是ResultSet結果集,若是直接用原生的讀取方式,你會看到以下代碼:
ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { Long id = resultSet.getLong("id"); String title = resultSet.getString("title"); String author = resultSet.getString("author"); String content = resultSet.getString("content"); ...... }
獲取到每一個字段的數據以後,而後經過反射的方式生成一個對象;Mybatis內部其實也是這樣實現的,封裝好經過簡單的配置便可獲取結果集,常見的結果集配置如resultMap,resultType等;Mybatis內部處理ResultSet是ResultSetHandler,其具體實現是DefaultResultSetHandler類:
public interface ResultSetHandler { <E> List<E> handleResultSets(Statement stmt) throws SQLException; <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException; void handleOutputParameters(CallableStatement cs) throws SQLException; }
處理接口中一共定一個了三個方法分別是:處理普通的結果集,處理遊標結果集,以及處理輸出參數,能夠大體看一下最經常使用的handleResultSets實現:
public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } ...如下省略... }
while循環遍歷結果集,ResultMap就是在XML中定義的結果集,好比定一個的是一個類型Blog,那麼會在處理結果的時候,首先建立一個對象,而後給對象屬性分配類型處理器TypeHandler,而後根據實際類型調用處理器的getResult方法:
public interface TypeHandler<T> { void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
能夠看到類型處理器分別用來處理設置參數和從結果集獲取參數,也就是分別處理了輸入和輸出;處理完以後其實就生成了XML中配置的結果集,多是一個對象,列表,hashMap等;另一個須要注意的地方就是xxMapper接口中定義的返回值須要保證和XML中配的結果集一致,否則當咱們經過代理對象返回結果集的時候會出現類型轉換異常;
XML的映射本文分三塊來介紹的,分別從Statement塊映射MappedStatement,參數映射ParameterMapping,以及結果集是如何經過DefaultResultSetHandler處理的;固然本文只是介紹了一個大概的映射流程,不少細節沒有講到,好比ResultHandler,RowBounds,緩存等;後面每一個細節都會單獨的寫一篇文章來介紹。