二、mybatis查詢分析

weixin_34138377發表於2018-09-18

上篇我們分析了mybatis在啟動過程中是如何載入配置檔案的,本篇我們繼續來分析一下mybatis在建立完Configuration配置後,是如何進行查詢的(本篇重點是查詢實現,mybatis的快取方面不會詳細分析,後續文章會涉及mybatis的快取)。

1、通過SqlSessionFactory建立SqlSession

我們上篇分析完了mybatis解析配置檔案建立Configuration,根據Configuration建立SqlSessionFactory的過程。現在,我們看一下mybatis是如何通過SqlSessionFactory建立SqlSession的。我們先看看SqlSessionFactory介面有哪些方法:

public interface SqlSessionFactory {
  SqlSession openSession();
  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);
  Configuration getConfiguration();
}

SqlSessionFactory顧名思義,是用來建立SqlSession的,我們看一下預設的DefaultSqlSessionFactory的實現,這裡只貼出主要的一個方法:

  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) 
        autoCommit = true;
      }      
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

我們可以看到,建立Sql需要Executor,而建立Executor需要Transaction物件,我們先來看一下Transaction介面:

public interface Transaction {

  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

Transaction介面給我們提供了事物基本操作的封裝。
而Executor也在Transaction的基礎上進行了封裝:

public interface Executor {
  ResultHandler NO_RESULT_HANDLER = null;
  int update(MappedStatement ms, Object parameter) throws SQLException;
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
  List<BatchResult> flushStatements() throws SQLException;
  void commit(boolean required) throws SQLException;
  void rollback(boolean required) throws SQLException;
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
  boolean isCached(MappedStatement ms, CacheKey key);
  void clearLocalCache();
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
  Transaction getTransaction();
  void close(boolean forceRollback);
  boolean isClosed();
  void setExecutorWrapper(Executor executor);
}

實現這兩個介面的之類有很多,所以我們暫時先不看這兩個介面的具體實現。在建立完這兩個介面的實現物件後,SqlSessionFactory會建立SqlSession。我們以DefaultSqlSession為例子,來看一下我們常用的幾個操作介面是如何實現的。

1、使用SqlSession運算元據庫的實現原理

1.1、使用selectOne()方法進行查詢

我們先來看一下方法的實現:

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

我們可以看到selectOne()方法是通過selectList()來實現的,當查詢出的結果多餘1個時就會跑出異常。那接下來我們看看selectList()的實現。

1.2、使用selectList()方法進行查詢

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

我們可以看到selectList的執行過程:首先根據id從configuration中查詢出對應的MappedStatement物件(上篇已經介紹了MappedStatement,這裡就不多說了),獲取到MappedStatement物件後,將MappedStatement物件和傳入的引數交給executor來執行具體的操作,我們以BaseExecutor為例,看一下具體的實現:

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @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;
  }

我們略過有關mybatis快取的操作,只看具體查詢執行的過程,我們可以看到executor查詢真正執行是通過呼叫queryFromDatabase()方法來實現的,我們一步一步跟蹤方法呼叫鏈會發現,queryFromDatabase()方法最後會呼叫子類的
doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) 方法實現的。Executor有三個子類:BatchExecutor、ReuseExecutor和SimpleExecutor,我們分別看一下三個子類對doQuery方法的實現:
首先我們看一下SimpleExecutor的實現:

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();
      //將查詢資料封裝成StatementHandler進行查詢
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

SimpleExecutor將資料封裝成StatementHandler來進行查詢,我們先來看一下封裝的過程:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    //呼叫mybatis生命週期中的方法,mybatis外掛應用
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
  
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }

  //RoutingStatementHandler的構造方法
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    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());
    }

  }

我們可以看到這裡直接建立了一個RoutingStatementHandler的物件,其實RoutingStatementHandler是一個代理物件,RoutingStatementHandler根據定義的StatementType來建立真正的StatementHandler。熟悉jdbc的同學應該對StatementType都不會陌生,StatementType有三種:1.Statement、2.PrepareStatement、3.CallableStatement。具體的區別這裡不多做解釋,不清楚的同學可以自己百度。三種不同的StatementType對應著SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。
我們繼續回到doQuery方法,在建立完StatementHandler後,SimpleExecutor會呼叫prepareStatement()方法來獲取Statement物件,然後將Statement物件交給StatementHandler來執行查詢,最後返回最終的結果。我們可以看到prepareStatement()方法分別呼叫了StatementHandler的prepare()方法和parameterize()方法來準備Statement。我們先來看一下prepare()方法的實現,這個方法在BaseStatementHandler這個抽象類中:

  public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // instantiateStatement方法是一個抽象方法,交給子類實現
      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
  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
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    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);
    }
  }

  // CallableStatementHandler
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getResultSetType() != null) {
      return connection.prepareCall(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareCall(sql);
    }
  }

PrepareStatemnet和CallableStatement都會先通過BoundSql物件來獲取真正執行的sql語句,這一塊解析我們後面再講。然後會通過jdbc的Connection來獲取Statement。
看完Statement的建立過程,我們繼續回到prepareStatement()方法,我們都知道,PrepareStatement方法是執行預編譯查詢的Statement,在執行查詢之前需要設定對應的引數,而CallableStatement用來執行儲存過程,也需要設定引數資訊,這些操作都是放在StatementHandler的parameterize()方法裡處理,我們以PrepareStatement為例子,看一下引數是如何設定的,通過方法追蹤,我們能夠找到設定引數是ParameterHandler的setParameters()方法實現的:

public void setParameters(PreparedStatement ps) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
      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 {
            value = metaObject == null ? null : metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          if (typeHandler == null) {
            throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
          }
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        }
      }
    }
  }

這裡根據解析mapper檔案的parameterType結果來進行引數的設定,具體邏輯並不複雜,有興趣的同學可以自己看一下。
這裡建立Statement的邏輯我們就看完了,繼續回到之前的public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) 方法,建立完Statement後,具體查詢還是會交給StatementHandler的query()方法來執行,StatementHandler,我們以PrepareStatementHandler為例看一下具體的實現:

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

執行完查詢之後,會把執行結果交給ResultSetHandler來解析,我們看一下具體實現方法:

  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<Object>();
    final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    int resultSetCount = 0;
    ResultSet rs = stmt.getResultSet();

    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -1) {
          // no more results.  Must be no resultset
          break;
        }
      }
    }

    validateResultMapsCount(rs, resultMapCount);
    while (rs != null && resultMapCount > resultSetCount) {
      final ResultMap resultMap = resultMaps.get(resultSetCount);
      ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
      //處理查詢結果結果
      handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
      rs = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    return collapseSingleResultList(multipleResults);
  }

邏輯很簡單,就是交給方法handleResultSet()方法來處理,我們一步一步跟蹤,發現處理會交給handleRowValues()方法進行處理,handleRowValues()處理的邏輯是首先跳過指定行數(RowBounds設定的,預設不跳過),然後遍歷ResultSet,這裡要注意,遍歷ResultSet的時候,遍歷有個上限,也是RowBounds指定,預設為Integer的最大值。在遍歷的過程中將結果集進行解析,我們看一下實現細節:

  protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
    final DefaultResultContext resultContext = new DefaultResultContext();
    //跳過指定行
    skipRows(rs, rowBounds);
    //限制遍歷結果集個數
    while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
      //選擇真正的ResultMap(mybatis的discriminator功能)
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
      //建立查詢結果物件
      Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
      //註冊到結果集中
      callResultHandler(resultHandler, resultContext, rowValue);
    }
  }

我們只看遍歷的過程,首先通過resolveDiscriminatedResultMap()方法獲取到真正要使用的ResultMap,這個過程很簡單,就是獲取discriminator 標籤定義的colum的值,然後獲取該值對應的ResultMap,但是要注意,這個是一個迴圈的過程(解析discriminator裡對映的ResultMap還有discriminator標籤)。這裡原始碼不貼出,有興趣的同學可以看一下。
獲取到對應的ResultMap後,通過getRowValue()來進行建立查詢結果物件,建立的過程可以概括為下面幾個步驟:
1、判斷要返回的結果的型別是否有TypeHandler,有的話使用TypeHandler來進行建立結果物件;
2、判斷resultMap中是否指定構造方法,有的話使用構造方法建立結果物件;
3、如果沒有TypeHandler和指定構造方法,使用objectFactory來進行建立物件;
4、建立完物件之後,如果不是通過TypeHandler進行建立,則需要設定物件的屬性值(是否進行引數設定還需要根據automapping屬性來進行判斷)
5、如果需要設定屬性,通過反射進行設定
6、通過反射設定resultMap中property屬性指定的對映的值

  protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
    final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
    //建立物件
    Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
    // 如果不是通過TypeHandler進行建立,則需要設定物件的屬性值
    if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(resultObject);
      boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
      if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
        final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
        //設定需要自動配置的屬性值
        foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
      }
      final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
      // 通過反射設定resultMap中property屬性指定的對映的值
      foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
      foundValues = (lazyLoader != null && lazyLoader.size() > 0) || foundValues;
      resultObject = foundValues ? resultObject : null;
      return resultObject;
    }
    return resultObject;
  }

將查詢結果解析成物件之後,繼續呼叫callResultHandler()來對結果集進一步處理,解析成map或list。這裡我們就不多解釋了。
到這裡SimpleExecutor就介紹完了,我們繼續看一下ReuseExecutor:ReuseExecutor顧名思義,是可以重用的Executor。ReuseExecutor會快取Statement,以sql為keyStatement為value快取,這裡不多介紹了。
最後我們看一下BatchExecutor:BatchExecutor繼承了BaseExecutor,來講多次查詢或者更新合併成一個。熟悉jdbc的同學應該對這個不陌生。這裡不多講,有興趣的同學也可以看一下,邏輯並不複雜。到這裡SqlSession的selectList()的方法的邏輯已經分析完了。接下來我們看看SqlSession的selectMap()方法。

1.3、使用selectMap()方法進行查詢

我們先看一下DefaultSqlSession的selectMap()方法:

  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List<?> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
        configuration.getObjectFactory(), configuration.getObjectWrapperFactory());
    final DefaultResultContext context = new DefaultResultContext();
    for (Object o : list) {
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    }
    Map<K, V> selectedMap = mapResultHandler.getMappedResults();
    return selectedMap;
  }

我們發現,selectMap()操作也是先進行selectList()查詢出結果集,再使用DefaultMapResultHandler來做一步轉換。邏輯也很簡單。有興趣的同學可以自己閱讀一下原始碼。

1.4、使用Mapper介面進行查詢

我們再使用mybatis的過程中,使用最多的操作應該是使用Mapper介面進行查詢:

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

我們看一下Mapper具體的獲取過程:

  //sqlSession的getMapper方法
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
    //configuration的getMapper方法
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  // mapperRegistry的getMapper方法
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    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);
    }
  }

獲取Mapper的過程就是從configuration中的MapperRegistry中獲取已經註冊的mapperProxyFactory,然後通過mapperProxyFactory獲取Mapper,註冊是發生在mybatis解析configuration的過程中。我們可以先來看一下mapper的註冊過程:

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

註冊過程很簡單,就是new一個MapperProxyFactory物件,我們看看MapperProxyFactory的實現:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

熟悉java反射的同學應該很容易看懂MapperProxyFactory。其中MapperProxy實現了InvocationHandler介面,作為代理的處理邏輯。我們可以看一下具體的實現過程:

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 (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    //獲取MapperMethod物件,使用MapperMethod來執行具體的操作
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

//獲取和構造MapperMethod物件
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

我們可以看一下MapperMethod的execute()方法:

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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;
  }

我們可以看到,在使用Mapper介面進行操作時,代理會根據方法的簽名資訊來選擇執行操作的型別,然後使用SqlSession來執行具體的操作。

好了,到此mybatis的查詢分析就介紹到這裡。為了總結上面的操作,我畫了一個簡單的時序圖幫助理解:


5441790-e32136a70a725081.png
test.png

相關文章