前言
在上一篇文章中我們分析了StatementHandler的源碼,本文我們會分析Mybatis中四大接口之一的ParameterHandler的源碼部分
源碼
ParameterHandler.java
參數(shù)處理器,用來給PreparedStatement設(shè)置參數(shù)的
接口定義
public interface ParameterHandler {
// 獲取當(dāng)前執(zhí)行實際傳入的參數(shù)
Object getParameterObject();
// 給PreparedStatement設(shè)置參數(shù)
void setParameters(PreparedStatement ps)
throws SQLException;
}
接口定義好了,活就需要有人來做了,下面看看ParameterHandler接口有哪些實現(xiàn)
DefaultParameterHandler.java
Mybatis僅僅提供了一個實現(xiàn)類,我們直接看其源碼

看其實現(xiàn)類的源碼之前,我們先設(shè)想一下,如果是讓你自己來實現(xiàn)這個接口,你的實現(xiàn)中應(yīng)該有哪些屬性呢? 確定實現(xiàn)有哪些屬性之前我們先仔細分析一下接口中定義的方法,接口方法只有兩個,一個獲取當(dāng)前實際執(zhí)行的參數(shù)對象,注意是這種形式的({{"phone", "15800000000"}, {"param1", "15800000000"}})
所以實現(xiàn)中必然需要有一個屬性,類似下面所示
// 當(dāng)前實際執(zhí)行前的參數(shù)對象
private final Object parameterObject;
除此之外的另一個接口方法是為PreparedStatement對象設(shè)置動態(tài)運行參數(shù),既然是設(shè)置參數(shù),我就需要拿到整個MappedStatement對象以及BoundSql對象,通過BoundSql我就可以得到帶有?號的sql語句,當(dāng)前傳遞的參數(shù)以及整個sql語句的參數(shù)映射關(guān)系List<ParameterMapping>,(這個映射關(guān)系尤為重要,可以說是ParameterHandler實現(xiàn)的關(guān)鍵),通過參數(shù)映射關(guān)系我就可以準確的找到哪個參數(shù)對應(yīng)哪個TypeHandler,以及該參數(shù)是入?yún)?/code>還是出參類型,其javaType是什么,jdbcType是什么等等信息
ok,分析到這里基本上差不多了,我們還是直接看源碼吧
public class DefaultParameterHandler implements ParameterHandler {
// 屬性
// 持有typeHandler注冊器
private final TypeHandlerRegistry typeHandlerRegistry;
// 持有MappedStatement實例,這是一個靜態(tài)的xml的一個數(shù)據(jù)庫操作節(jié)點的靜態(tài)信息而已
private final MappedStatement mappedStatement;
// 持有當(dāng)前操作傳入的實際參數(shù)
private final Object parameterObject;
// 動態(tài)語言被執(zhí)行后的結(jié)果sql
private final BoundSql boundSql;
private final Configuration configuration;
// 構(gòu)造函數(shù)
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
// 實現(xiàn)方法
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 1. 獲取boundSql中的參數(shù)映射信息列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 1.1. 遍歷參數(shù)映射列表,這個列表信息就是我們xml文件中定義的某個查詢語句的所有參數(shù)映射信息,注意這個List中的參數(shù)映射元素的順序是和真實xml中sql的參數(shù)順序?qū)?yīng)的
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 1.2. 只有入?yún)㈩愋筒艜O(shè)置PreparedStatement
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 取出參數(shù)名,這里比如說是'phone'
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 {
// 1.3. 這一步的工作就是從當(dāng)前實際傳入的參數(shù)中獲取到指定key('phone')的value值,比如是'15800000000'
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 2. 獲取該參數(shù)對應(yīng)的typeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 2.1. 獲取該參數(shù)對應(yīng)的jdbcType
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 3. 重點是調(diào)用每個參數(shù)對應(yīng)的typeHandler的setParameter方法為該ps設(shè)置正確的參數(shù)值
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);
}
}
}
}
}
}
這里截取一個運行時的debug信息看一下就知道了

至此我們可以知道,每一次的調(diào)用查詢(數(shù)據(jù)庫查詢,不走Mybatis緩存),Executor在執(zhí)行doQuery的時候都會創(chuàng)建一個StatementHandler實例,每個StatementHandler在實例化的時候,都會創(chuàng)建并持有兩個處理器即ParameterHandler和ResultSetHandler
這樣statementHandler就可以利用ParameterHandler完成預(yù)處理語句的參數(shù)化設(shè)置,以及結(jié)果查詢出來以后再利用ResultSetHandler處理結(jié)果集
這里我們再次貼一下BaseStatementHandler的構(gòu)造函數(shù)部分源碼
// BaseStatementHandler.java
// ...
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 省略其他屬性設(shè)置...
// 1. parameterHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 2. resultSetHandler
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
我們看下parameterHandler的創(chuàng)建過程,可以看到是利用了configuration
// Configuration.java
// ...
// new一個參數(shù)化處理器
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 1. 內(nèi)部就是調(diào)用了DefaultParameterHandler的構(gòu)造方法,new了一個實例返回
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 2. 看到這里,又是Mybatis的插件,提供給開發(fā)人員攔截ParameterHandler的調(diào)用邏輯
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
代碼分析至此,我們簡單總結(jié)一下
當(dāng)真實的數(shù)據(jù)庫查詢被執(zhí)行時,
SimpleExecutor執(zhí)行器會通過configuration對象new出一個StatementHandler出來,后續(xù)的所有操作都是利用這個statementHandler來完成,這個handler實際上是返回了經(jīng)過開發(fā)人員自定義插件的代理類,可以基于此處自定義插件當(dāng)statementHandler實例有了以后,我們需要創(chuàng)建出真實的JDBC
Statement實例,此處調(diào)用了statementHandler的prepare方法(此處可自定義插件攔截哦)實現(xiàn),根據(jù)handler的類型不同則會創(chuàng)建不同的JDBCStatement實例返回,這里簡單的提一下,如果是ReuseExecutor不會為同一個BoundSql中的sql語句創(chuàng)建多個prepareStatement返回,而是在內(nèi)部維護一個Map<String, Statement>來重用Statement實例當(dāng)statement實例返回時,就會進行
參數(shù)化設(shè)置(如果是一個Statement實例而非PrepareStatement實例,參數(shù)化設(shè)置方法的內(nèi)部就不作任何處理),調(diào)用statementHandler的parameterize方法完成,由于我們每個statementHandler都持有兩個處理器即ParameterHandler和ResultSetHandler,這個時候就是ParameterHandler上場的時候了,PrepareStatementHandler類型的handler在參數(shù)化方法內(nèi)部就是委托給了ParameterHandler,調(diào)用了其setParameters方法進行參數(shù)化設(shè)置,而setParameters方法的核心實現(xiàn)就是拿到當(dāng)前調(diào)用的boundSql實例中的parameterMappings集合和parameterObject實例,通過遍歷這個集合,對所有入?yún)㈩愋?ParameterMode.IN)的參數(shù)進行逐個獲取其參數(shù)對應(yīng)的TypeHandler,再利用TypeHandler的setParameter方法對當(dāng)前的preparedStatement實例進行參數(shù)化設(shè)置,而設(shè)置需要的參數(shù)值就是從parameterObject中獲取的當(dāng)參數(shù)設(shè)置完成以后,statementHandler又拿到了調(diào)用權(quán),開始調(diào)用
query/update(Statement)方法執(zhí)行數(shù)據(jù)庫操作,不同的handler則有不同的實現(xiàn),比如SimpleStatementHandler就是調(diào)用的Statement.execute(sql)執(zhí)行,而PreparedStatementHandler則是直接ps.execute()執(zhí)行即可數(shù)據(jù)庫執(zhí)行完成后,就拿到了
結(jié)果集,這個時候就是ResultSetHandler發(fā)揮的時候了,下文我們會繼續(xù)分析ResultSetHandler的源碼