Mybatis源碼分析之接口調用

上一章已經學習了初始化的過程,經過讀取配置文件的形式已經得到了SqlSessionFactory,該對象持有Configuration。 給出總的時序圖,帶着問題來看源碼sql

先寫一個簡單的例子

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    ProductMapper productMapper = sqlSession.getMapper(ProductMapper.class);
    List<Product> productList = productMapper.selectProductList();
    for (Product product : productList) {
    System.out.printf(product.toString());
    }
} finally {
    sqlSession.close();
}
複製代碼

在調用mapper的方法以前,第一步必定是先建立一個sqlsession,而後根據獲取的到sqlsession來獲取mapper代理對象。這裏就用到了初始化中的knowMappers,經過傳入的ProductMapper.class獲取到初始化時保存的MapperProxyFactory,而且經過mapperProxyFactory.newInstance(sqlSession);來獲取mapperProxy代理對象緩存

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);
    }
  }
複製代碼

當調用了代理對象的某一個代理函數後,這個調用請求首先會被髮送給代理對象處理類MapperProxy的invoke()函數,MapperProxy實現了InvocationHandler,重寫其invoke方法bash

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
複製代碼

實際invoke方法在調用以前先緩存了method,保存在MapperProxy類的一個名爲methodCache的Map中,並返回一個MapperMethod對象,而後開始調用excute方法。session

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
    	Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        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 if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        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;
  }
複製代碼

能夠看到excute方法根據不一樣操做調用了不一樣的方法,在這以select語句多條結果的sql爲例。因此走的是app

result = executeForMany(sqlSession, args);
複製代碼

executeForMany調用了sqlSession.<E>selectList方法來完成功能,實際是調用了sqlSessionProxy的代理ide

public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.<E> selectList(statement, parameter);
}
複製代碼

通過動態代理調用以後,來到了DefaultSqlSessionselectList方法函數

@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();
    }
  }
複製代碼

能夠看到該方法從configuration獲取了MappedStatement,這個方法是經過Map的get方法獲取到對應的MappedStatement,獲取到以後調用executor.query方法,獲取到BoundSql即綁定的sql學習

public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      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;
  }
複製代碼

在獲取到BoundSql以後在進行excutor.query方法,這個方法的底層其實就是PreparedStatement,封裝在下面這段代碼的queryFromDatabase方法裏面,內部的具體調用就再也不詳細講解,因此說Mybatis就是底層jdbc的封裝。ui

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();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
複製代碼

最後的結果實際上是個list,結果以object返回。this

最後來回顧一下調用過程,首先經過SqlSessionFactory獲取sqlsession,經過sqlsessionknowMappers中查詢MapperProxy代理對象,調用其invoke方法,該方法其實用調用了MapperMethodinvoke,最後這個方法的底層實現其實就是jdbc封裝。

相關文章
相關標籤/搜索