Mybatis原始碼詳解之介面方法被執行流程原始碼解析
與上一篇Mybatis原始碼解析的部落格已經隔了好長一段時間,最近發生了一些亂七八糟糟心的事情,甚至每天加班,沒來得及寫點什麼,最近一個月的學習是亂的一塌糊塗。
接著上一篇的分析,上一篇完成了所有配置檔案的解析,將各個配置檔案都解析到一個叫Configuration的類裡,這些就是介面方法可以被執行的元資料,任何一個方法的執行必然依賴於此。介面方法執行流程就是怎樣使用這些元資料呢?
還是以最開始的例項工程引入方法執行,相信已經走到這裡了,也肯定知道介面方法的執行就是對介面的一個動態代理。
還記得上一篇部落格中addMapper函式,最終把介面註冊到一個Map快取中,即knownMappers。
下面開始今天的分析:
(1)對介面實現動態代理
IPlayDao play = sqlSession.getMapper(IPlayDao.class);
主要就是這句,經過getMapper方法以後,返回的已經不是介面了,而是一個經過動態代理的代理類了。
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } //沒有什麼值得分析的,前面兩個類就是帶著傳入的介面層層傳遞 @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //這裡就是上一篇一個mapper.xml檔案中,namespace介面註冊的一個快取,從這個快取中用介面提取出代理工廠 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) throw new BindingException("Type " + type + " is not known to the MapperRegistry."); try { //動態代理的玄機就在這個工廠類裡 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
帶著介面步步深入,看到了proxy工廠,沒有什麼難易理解的部分,主要就是那個快取中取代理工廠,這是初始化中非常關鍵的一個點。還有就是sqlSession的傳遞,也算是個關鍵,這塊兒應該也算一種命令模式,因為未來不知道要呼叫介面的什麼方法,所有要執行該方法的流程最終需要藉助於sqlSession的分配,所以此處帶著它層層傳遞,sqlSession是規劃執行介面方法的總管。接著看代理工廠是怎麼對所有的介面統一處理的。
public T newInstance(SqlSession sqlSession) { //sqlsession,介面都傳入到了MapperProxy裡,這個類就是實現InvocationHandler的類,methodCache這裡傳入一個空的引數 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { //看到了動態代理的例項化部分,相當於介面被例項化了,這就是getMapper最終返回的一個介面代理 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
顯然,所有的介面子這個工廠類裡被代理了,下一次呼叫介面中的方法的時候,直接進入了MapperProxy類的invoke方法中了,MapperProxy類裡也包含了所有用到的屬性,知道是屬於哪個介面的代理,還有sqlSession屬性,該屬性中包含有Configuration,Executor。下面接著分析這個MapperProxy類。
(2)動態代理的統一處理,invoke方法
Ball ball = play.selectBall("1");
呼叫介面的方法,直接跳轉到MapperProxy類的invoke方法,具體看一下:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//首先判斷該方法所屬的類是否是一個物件,如果是直接反射呼叫該方法,顯然,這裡傳遞進來的是介面,不會進入if分支
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//這裡將傳入的方法包裝成為一個MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
//包裝每個被呼叫的方法
private MapperMethod cachedMapperMethod(Method method) {
//這就是當時代理工廠傳進來的空的methodCache引數,就是一個簡單的Map快取,提高效率,只要被呼叫過一次就不會重新包裝,直接從快取中取
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//方法包裝
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
這裡就是動態代理的最關鍵類了,看起來是不是其實也很簡單,下面看一下方法的包裝裡做了些什麼。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//傳入的三大引數構造出兩個內部類,類似於初始化,封裝資源的一個過程,後面分析一下這兩個內部類
//這個是提取以前初始化時候註冊的MappedStatement
this.command = new SqlCommand(config, mapperInterface, method);
//這個是對被呼叫方法的一個封裝提取
this.method = new MethodSignature(config, method);
}
//SqlCommand建構函式
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
//還記得在構建MappedStatement的過程中,有這麼一句註冊configuration.addMappedStatement(statement);
//這裡就是在構造提取MappedStatement的Map中的key
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
//提取出ms,初始化了些引數
name = ms.getId();//其實這個就是mapper.xml中每個sql節點的id,也就是方法名
type = ms.getSqlCommandType(); //這個是該條sql語句的標籤,是insert|select|delete|update
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
//MethodSignature的建構函式,就是對呼叫方法的一個解剖封裝,提取各項引數
public MethodSignature(Configuration configuration, Method method) throws BindingException {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.hasNamedParameters = hasNamedParams(method);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
這裡其實也可以看作是一個初始化的過程,在執行方法之前,提取資源庫中的各種該方法應用的資源
(3)開始方法呼叫的執行
在mapperProxy的invoke方法最後一句,即呼叫封裝後的MapperMethod的execute方法,從這裡展開了介面方法的具體執行
//傳入sqlSession和待執行方法的引數(也就是sql語句中需要的引數(PrepareStatement的引數))
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//首先根據封裝該方法時候所封裝的type判斷是什麼型別的sql語句, 選擇不同的方法處理
//insert
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
//update
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
//delete
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
//select 講解以select為例子,各中sql語句的流程基本一樣
} else if (SqlCommandType.SELECT == command.getType()) {
//判斷是select語句以後,還有不同的情況,就是封裝方法時候的另一個類,methodsigature中的欄位再次判斷判斷
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);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
這裡仍舊將sqlSession帶在方法的引數裡往下一級傳遞。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
//作了一個簡單處理,如果有多個引數,將其封裝在一個Map裡
Object param = method.convertArgsToSqlCommandParam(args);
//如果method有分頁資訊,進if分支,大多數情況不會用系統自帶分頁,都是自己寫分頁攔截器實現分頁
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
//sqlSession開啟了真正的運籌帷幄的時候了(也就是命令模式應用的時候了,封裝了各種功能,可以統一處理增刪改查)
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
終於到了sqlSession呼叫它方法的時候了。即將運籌它下面的四大物件來完成呼叫流程。
//第一個引數statement,忘記了沒有,封裝方法的時候(SqlCommand類中),就是介面名.方法名;
//第二個引數就是sql語句中缺的引數
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//用這個statement從資源類中取出MappedStatement,這個類包含了mapper.xml檔案中一個sql節點的所有資訊
MappedStatement ms = configuration.getMappedStatement(statement);
//executor是什麼,執行器,他來排程其他三個StatementHandler,ParameterHandler,ResultHandler來執行sql語句,
//他是怎麼來的呢,在初始化出來sqlSession的時候就newExecutor過了,如果不傳入特殊的Executor,會有一個預設的執行器
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
解析來到了Executor中,執行BaseExecutor中的qurey方法
//前兩個引數,一個是sql節點,一個是傳入的引數,後兩個為null
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//進來首先獲取BoundSql,這個BoundSql是什麼呢,他就是建立sql和引數的地方,呼叫ms的方法獲取,下面具體看怎麼獲取
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public BoundSql getBoundSql(Object parameterObject) {
//ms中沒幹什麼,直接將其轉入下一層,sqlSource中獲取,還記得初始化收構建SqlSource嗎,這裡確實是一個難點,下面再進入到SqlSource類
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.size() <= 0) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
//這裡是一個DynamicSqlSource的getBoundSql方法(mybatis中大多數的sql都是帶有where,if等各種條件的也就是動態的)
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
這裡的邏輯有一點沒一步步分析清楚,總之最後將各個條件合併,封裝出一個BoundSql,其裡面已經包含了sql語句執行的所有的完整內容,等後面會繼續對這部分完善,逐步分析到底是怎麼一步步將sql語句拼接,封裝的。
拿到boundSql以後調到另一個query方法中
//看這個方法,其實什麼也沒做,只是些邏輯判斷或者安全處理,關鍵的一步就是又轉到了另一個函式queryFromDatabase
@SuppressWarnings("unchecked")
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
//這個方法也是些邏輯判斷等簡單處理,就不細糾了,咱們只是跟蹤執行流程,下面看到doQuery方法, 這個就是Executor中真正做事的方法了
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
上面一些流程沒有什麼實質性的內容,下面主要分析doQuery方法,這個方法是執行器中的真正方法了, 在baseExecutor中是個抽象方法,根據自己配置的Executor,執行不同的查詢方法,大多數時候只需要使用預設執行器即可,即SimpleExecutor
/**
*
* @param ms 被執行方法對應的sql節點全部資訊
* @param parameter sql引數
* @param rowBounds null
* @param resultHandler null
* @param boundSql sql語句
* @return
* @throws SQLException
*/
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//取到資源類
Configuration configuration = ms.getConfiguration();
//這句創建出執行sql的關鍵類,整個流程會創建出ParameterHandler,ResultHandler,下面看詳細流程
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
//各個引數於上面一樣
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//RoutingStatementHandler不是我們真實服務的物件,它是用介面卡模式找到對應的StatementHandler來執行,RoutingStatementHandler繼承自StatementHandler,
//有一個StatementHandler屬性,一種特殊的介面卡模式,具體選擇哪種還是靠簡單工廠來選擇
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//Mybatis外掛,前面部落格專門講解過
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//簡單工廠模式選擇合適的StatementHandler,與三種執行器相對應,這裡StatementHandler對應這jdbc的Statement,PrepareStatement.
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
//對應JDBC的prepareStatement
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
//建構函式
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
//其他兩大物件,引數和結果集處理器,建立的過程就不看了, 比較簡單,Mybatis都有ParameterHandler和ResultSetHandler的default實現
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
newStatementHadler的過程將sql語句執行的前置條件都準備好了,到這裡四大物件已經都初始化完成,下面就是利用四大物件來完成執行的時候了。下面看doQuery的後兩句內容。
stmt = prepareStatement(handler, ms.getStatementLog());
其實執行sql語句,最終還是用jdk的jdbc來處理,前面只是為了便利對他的各種封裝,下面又即將要看到他的廬山真面目了,其實所說的基礎知識最重要,也就重要在這種地方,最終的本質肯定是萬變不離其宗,紮實的基礎會對理解框架的核心部分事半功倍。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
//這個就是jdbc的statement,這裡就看到了熟悉的味道
Statement stmt;
//connection,從我們配置檔案初始化中初始化的dataSource取得連線,具體和jdbc的處理一樣
Connection connection = getConnection(statementLog);
//用connection建立statement
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
//BaseStatementHandler
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//建立statement,Statement和PrepareStatement的建立略有不同,後者在建立的時候
//需要將sql語句模板傳進去,也就是兩者使用sql的時機不同,這個方法是個抽象方法,根據不同的StatementHandler
//選擇不同的處理方式,下面具體看
statement = instantiateStatement(connection);
setStatementTimeout(statement);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
建立Statement,下面看下instantiateStatement方法
//SimpleStatementHandler中的是實現,這個沒什麼邏輯直接建立Statement
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() != null) {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.createStatement();
}
}
//PreparedStatementHandler中的實現,這個建立PrepareStatement提前需要sql
protected Statement instantiateStatement(Connection connection) throws SQLException {
//從boundSql中取得sql
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
邏輯也比較簡單,只要對JDBC的邏輯過程比較熟悉,這裡都是一樣的,經過這個方法以後就從Connection中取到了Statement或者PreparedStatement
接著就是像jdbc一樣設定sql模板的引數值,當然如果是Statement的話就不需要設定了,看下Mybatis是怎麼處理的。
handler.parameterize(stmt);
這裡就是為sql設定引數,下面看下不同的引數Handler是怎麼處理的
//SimpleStatementHandler中不需要設定,空函式
public void parameterize(Statement statement) throws SQLException {
// N/A
}
//PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
//newParameterHandler的時候初始化了DefaultParameterHnadler,這裡用初始化時候的引數組裝sql中的引數之
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//取得sql語句和類對應的資料型別
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();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
下面就剩下最後一步,執行sql語句了,不論是prepareStatement還是Statement都統一處理完了sql語句,剩下執行了
handler.<E>query(stmt, resultHandler);
大家發現沒有,之前的所有邏輯是在Executor中處理,後來所有的這些組裝過程都是在StatementHandler中處理的,有種感覺就是Executor包含者StatementHandler,將邏輯轉到StatementHandler以後,感覺StatementHandler中包含著ParameterHandler和ResultSetHandler.
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
比較簡單,就不分析了,直接呼叫jdbc的函式,執行sql語句,對sql語句的執行結果Mybatis又進行了封裝,也就是ResultSetHandler的工作開始了
public List<Object> handleResultSets(Statement stmt) throws SQLException {
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++;
}
String[] resultSets = mappedStatement.getResulSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
最終封裝為一個list返回了,到這裡整個方法的執行就結束了,其實看起來整個邏輯也沒這麼複雜,最後的關鍵部分都是對jcbc的封裝,確實基礎很關鍵。還有比較關鍵的一個部分就是sql語句的拼接部分,也就是BoundSql的產生過程,後面還值得分析理解。
做一個對上面過程簡單的總結梳理:SqlSession是通過Executor建立StatementHandler來執行的,而StatementHandler經過下面三步來執行sql
(1)prepared預編譯SQL;
(2)parameterize設定引數;
(3)query執行sql
parameterize是呼叫parameterHandler的方法去設定的,而引數是根據型別處理器typeHandler去處理的。query方法是通過resultHandler進行結果封裝的,如果是update直接返回證書,否則它通過typeHandler處理結果型別,然後用ObjectFactory提供的規則組裝物件,返回給呼叫者。
下一篇文章準備寫一下Mybatis和Spring的連線部分。