1. 程式人生 > >Mybatis原始碼詳解之介面方法被執行流程原始碼解析

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的連線部分。