1. 程式人生 > >mybatis原始碼 (三) —— mapper 動態代理以及select操作

mybatis原始碼 (三) —— mapper 動態代理以及select操作

String resource = "com/analyze/mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper
(UserMapper.class); Map map = userMapper.getUA(); sqlSession.close();

org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper

  @Override
  public <T> T getMapper(Class<T> type) {
    //最後會去呼叫MapperRegistry.getMapper
    return configuration.<T>getMapper(type, this);
  }

org.apache.ibatis.binding.MapperRegistry#getMapper

  //返回代理類
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //從map中去取MapperProxyFactory
    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); } }

org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)

  protected T newInstance(MapperProxy<T> mapperProxy) {
    //用JDK自帶的動態代理生成對映器
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    //建立一個MapperProxy物件
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

最終呼叫代理類mapper的方法會走invoker的invoke方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //代理以後,所有Mapper的方法呼叫時,都會呼叫這個invoke方法
    //並不是任何一個方法都需要執行呼叫代理物件進行執行,如果這個方法是Object中通用的方法(toString、hashCode等)無需執行
    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);
  }

org.apache.ibatis.binding.MapperProxy#cachedMapperMethod

  //去快取中找MapperMethod
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      //找不到才去new
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      //存入map
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

MapperMethod構造方法

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
  }

org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand構造,這個是關鍵,覺得是什麼型別的sql

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      //mapperInterface.getName(即mapper中配置的package)
      // method.getName() mapper中配置的id
      //拼在一起就是Configuration.mappedStatements中存的key
      String statementName = mapperInterface.getName() + "." + method.getName();
      MappedStatement ms = null;
      if (configuration.hasStatement(statementName)) {
        //取出來  MappedStatement
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
        //如果不是這個mapper介面的方法,再去查父類
        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);
      }
      name = ms.getId();
      //賦值sql的型別是select update
      type = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        throw new BindingException("Unknown execution method for: " + name);
      }
    }

這一段主要是為了把mapper介面和mapper配置檔案的資訊關聯起來
org.apache.ibatis.binding.MapperMethod#execute

  //執行
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //可以看到執行時就是4種情況,insert|update|delete|select,分別呼叫SqlSession的4大類方法
    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()) {
        //如果結果是map
        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;
  }

這段程式碼主要就是判斷是什麼型別的sql,執行怎樣的邏輯,我們選取一段程式碼來分析

        //否則就是一條記錄
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);

org.apache.ibatis.binding.MapperMethod.MethodSignature#convertArgsToSqlCommandParam
把方法引數轉換成sql引數

   public Object convertArgsToSqlCommandParam(Object[] args) {
      final int paramCount = params.size();
      if (args == null || paramCount == 0) {
        //如果沒引數
        return null;
      } else if (!hasNamedParameters && paramCount == 1) {
        //如果只有一個引數
        return args[params.keySet().iterator().next().intValue()];
      } else {
        //否則,返回一個ParamMap,修改引數名,引數名就是其位置
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : params.entrySet()) {
          //1.先加一個#{0},#{1},#{2}...引數
          param.put(entry.getValue(), args[entry.getKey().intValue()]);
          // issue #71, add param names as param1, param2...but ensure backward compatibility
          final String genericParamName = "param" + String.valueOf(i + 1);
          if (!param.containsKey(genericParamName)) {
            //2.再加一個#{param1},#{param2}...引數
            //你可以傳遞多個引數給一個對映器方法。如果你這樣做了, 
            //預設情況下它們將會以它們在引數列表中的位置來命名,比如:#{param1},#{param2}等。
            //如果你想改變引數的名稱(只在多引數情況下) ,那麼你可以在引數上使用@Param(“paramName”)註解。 
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }

然後呼叫result = sqlSession.selectOne(command.getName(), param);來執行sql,這裡command.getName()其實就是configuration裡面map中存的{key:namespace+id value:sql}得key即id
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)

  //核心selectList
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根據statement id找到對應的MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      //轉而用執行器來查詢結果,注意這裡傳入的ResultHandler是null
      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();
    }
  }

org.apache.ibatis.executor.BaseExecutor#query

  //SqlSession.selectList會呼叫此方法
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //得到繫結sql
    BoundSql boundSql = ms.getBoundSql(parameter);
    //建立快取Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //查詢
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
  @Override
  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.");
    }
    //先清區域性快取,再查詢.但僅查詢堆疊為0,才清。為了處理遞迴呼叫
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      //加一,這樣遞迴呼叫到上面的時候就不會再清區域性快取了
      queryStack++;
      //先根據cachekey從localCache去查
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //若查到localCache快取,處理localOutputParameterCache
        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
        //如果是STATEMENT,清本地快取
        clearLocalCache();
      }
    }
    return list;
  }

org.apache.ibatis.executor.BaseExecutor#queryFromDatabase

  //從資料庫查
  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);
    //如果是儲存過程,OUT引數也加入快取
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

org.apache.ibatis.executor.SimpleExecutor#doQuery

  //select
  @Override
  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
      //這裡看到ResultHandler傳入了
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //準備語句
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler.query
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

org.apache.ibatis.executor.SimpleExecutor#prepareStatement

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //呼叫StatementHandler.prepare
    stmt = handler.prepare(connection);
    //呼叫StatementHandler.parameterize
    handler.parameterize(stmt);
    return stmt;
  }

最後呼叫的connection 來進行sql
再看org.apache.ibatis.executor.CachingExecutor#update
在進行update操作的時候,會先把快取給清空

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    //重新整理快取完再update
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }