mybatis 源碼分析之執行sql

  • mabatis 如何解析mapper.xml文件裏的if等標籤,生成執行語句?設置參數?
  • 下面通過跟蹤源碼我們來查看下具體是如何執行。
  • 這裏我們可以看到我們的mapper接口是通過MapperProxy 生成的代理對象進行調用的。即諸如的是一個動態代理對象。

 

 public class MapperProxy<T> implements InvocationHandler, Serializable {


      // 反射執行方法
 @Override
  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 (method.isDefault()) {
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }

// 生成並緩存MapperMethod  對象。method 爲key,value 是MapperMethod 。這樣就建立了method到
//MapperMethod 的映射關係。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 執行調用
    return mapperMethod.execute(sqlSession, args);
  }



}

public class MapperMethod {
        
    
 //    
  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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

}



 private class SqlSessionInterceptor implements InvocationHandler {
     // 攔截並獲取sqlsession執行調用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

public class DefaultSqlSession implements SqlSession {

@Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
       // 執行器調用update方法做更新
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
public class CachingExecutor implements Executor {
        // 緩存執行器調用的是其他是那種執行器的一種,即簡單執行器  ,批量執行器,reuse 執行器。
      private final Executor delegate;
//緩存執行器,默認二級緩存打開。
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

}
    

// 向下繼續追蹤,我們可以看到sqlNode 節點是我們把標籤解析爲sql的地方。靜態sql。
public class StaticTextSqlNode implements SqlNode {
  private final String text;

  public StaticTextSqlNode(String text) {
    this.text = text;
  }

  @Override
  public boolean apply(DynamicContext context) {
        //靜態sql直接添加
    context.appendSql(text);
    return true;
  }

}

}

// foreach 標籤解析  
public class ForEachSqlNode implements SqlNode {
  public static final String ITEM_PREFIX = "__frch_";

  public boolean apply(DynamicContext context) {
    Map<String, Object> bindings = context.getBindings();
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    applyOpen(context);
    int i = 0;
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      if (first || separator == null) {
        context = new PrefixedContext(context, "");
      } else {
        context = new PrefixedContext(context, separator);
      }
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709
      if (o instanceof Map.Entry) {
        @SuppressWarnings("unchecked")
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
        applyIndex(context, i, uniqueNumber);
        applyItem(context, o, uniqueNumber);
      }
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    applyClose(context);
    context.getBindings().remove(item);
    context.getBindings().remove(index);
    return true;
  }
}
 
// 標籤解析的根
public class MixedSqlNode implements SqlNode {
  private final List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    // 循環解析
    contents.forEach(node -> node.apply(context));
    return true;
  }
}

    // DynamicSqlSource 的getBoundSql 方法
 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();
    // parse解析,將形參去除,形成帶?的sql.
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    //構建攜帶參數,帶? sql 的對象。
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }


// DefaultParameterHandler 的參數設置方法
 public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    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();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

  // 類型轉換器,參數設置方法 BaseTypeHandler  jdbc 類型到java類型的轉換
  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
              + "Cause: " + e, e);
      }
    } else {
      try {
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different configuration property. "
              + "Cause: " + e, e);
      }
    }
  }
  • 標籤解析後的:foreach 解析
  • 通過以上源碼來看: 在上一篇我們解析完成後,MappedStatement的對象裏的 private SqlSource sqlSource; 屬性裏記錄了DynamicSqlSource 實現SqlSource  接口,其屬性rootSqlNode 記錄瞭解析的SqlNode。
    private final SqlNode rootSqlNode;  //MixedSqlNode  
  • public class MixedSqlNode implements SqlNode {
      private final List<SqlNode> contents;
  • }
  • 具體的標籤解析器配合具體的入參來解析生成對應的sql.具體的標籤解析器實現了SqlNode接口。
  • public interface SqlNode {
      boolean apply(DynamicContext context);
    }

     

  • 參數設置:由類型轉換器來設置參數,BaseTypeHandler基礎的類型轉換器實現TypeHandler<T> 接口。其他的轉換器繼承該基類。
  • 由以上代碼,我們可以提取一些關鍵的類。
  • Configuration: 配置類及容器。mybatis啓動後構建這個對象時就會把所有相關的信息作爲該對象的屬性保存下來。
  • 可以說 :這個類是mybatis的核心基礎類,是一個mybatis的容器。
  • MapperProxy : mapper接口生成動態代理對象的類。該類可以生成mapper接口的動態代理對象,從而注入。該對象包含了sqlSession, mapperInterface, methodCache 這三個屬性。
  • MapperRegistry : 這個類維護了mapper接口和其動態代理對象工廠(MapperProxyFactory)的映射關係。
  • MapperProxyFactory: Mapper代理工廠負責生成MapperProxy  對象。也記錄了Method, MapperMethod的映射緩存。
  • MapperMethod : 記錄了了sql執行命令和方法簽名。其execute 方法根據不同的命令,調用SqlSession的執行方法。
  • DefaultSqlSession: 實現了SqlSession接口,記錄了Executor執行器,Configuration 配置類,具體的執行交給執行器執行sql語句。
  • SimpleExecutor: 簡單執行器。執行簡單的增刪改查。
  • ReuseExecutor: 複用Statement 對象的執行器。緩存了sql與Statement 對象的映射關係。
  • BatchExecutor: 批量操作執行器。
  • CachingExecutor: 緩存執行器,開啓二級緩存後使用,該執行器是加了緩存功能。執行sql的還是上邊三種的一個。典型的裝飾模式應用。
  • TransactionalCacheManage:二級緩存開啓後,使用它保存緩存。
  • 推薦大家可以看一本書:《myBatis3源碼深度解析》參照書可以來自己跟蹤源碼,帶上問題去分析。
  • 通過源碼我們也可以提出一些問題用於面試,比如 mybatis有哪些執行器?執行器概念?執行器的區別?
  • 如何設置參數的?怎麼解析我們的foreach,if等標籤??
  • # 和$ 的解析有啥區別?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章