开发调试时,配置了log4j2文件:

<Logger name="com.xxx" level="TRACE" additivity="false">
    <AppenderRef ref="Console" />
    <AppenderRef ref="ErrorRollingFile" />
</Logger>
<Root level="INFO">
    <AppenderRef ref="Console" />
</Root>

将项目路径的日志级别设置为TRACE,其他为INFO,但是调试时,发现mybatis(3.4.6)的trace日志也都打印出来了。
根据日志内容可以看到是在org.apache.ibatis.logging.jdbc.BaseJdbcLogger第165行打印的日志,该行的源代码如下:

protected void trace(String text, boolean input) {
  if (statementLog.isTraceEnabled()) {
    statementLog.trace(prefix(input) + text);
  }
}

statementLog对象支持TRACE级别的日志打印,那么statementLog是哪里生成的?从源代码里可以看到是构建BaseJdbcLogger对象时,传入的参数。

/*
 * Default constructor
 */
public BaseJdbcLogger(Log log, int queryStack) {
  this.statementLog = log;
  if (queryStack == 0) {
    this.queryStack = 1;
  } else {
    this.queryStack = queryStack;
  }
}

继续根据代码,看传入的Log对象时从哪里创建的。
83309988.png
BaseJdbcLogger的子类中挑选ConnectionLogger继续跟进到BaseExecutor

protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

83569693.png
getConnection调用方中挑选SimpleExecutor.doQuery继续跟进:

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

@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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

从代码中可以看到,StatementLog是从MappedStatement对象中获取,那么现在问题就变成了MappedStatement是从哪里创建的。从doQuery方法一层层往上跟进到DefaultSession

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.getMappedStatement(statement)

public MappedStatement getMappedStatement(String id) {
  return this.getMappedStatement(id, true);
}

public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
  if (validateIncompleteStatements) {
    buildAllStatements();
  }
  return mappedStatements.get(id);
}
protected void buildAllStatements() {
  if (!incompleteResultMaps.isEmpty()) {
    synchronized (incompleteResultMaps) {
      // This always throws a BuilderException.
      incompleteResultMaps.iterator().next().resolve();
    }
  }
  if (!incompleteCacheRefs.isEmpty()) {
    synchronized (incompleteCacheRefs) {
      // This always throws a BuilderException.
      incompleteCacheRefs.iterator().next().resolveCacheRef();
    }
  }
  if (!incompleteStatements.isEmpty()) {
    synchronized (incompleteStatements) {
      // This always throws a BuilderException.
      incompleteStatements.iterator().next().parseStatementNode();
    }
  }
  if (!incompleteMethods.isEmpty()) {
    synchronized (incompleteMethods) {
      // This always throws a BuilderException.
      incompleteMethods.iterator().next().resolve();
    }
  }
}

项目用的是接口注解的方式,在最后的incompleteMethods.iterator().next().resolve()

public class MethodResolver {
  private final MapperAnnotationBuilder annotationBuilder;
  private final Method method;

  public MethodResolver(MapperAnnotationBuilder annotationBuilder, Method method) {
    this.annotationBuilder = annotationBuilder;
    this.method = method;
  }

  public void resolve() {
    annotationBuilder.parseStatement(method);
  }

}

进入MapperAnnotationBuilder.parseStatement方法

void parseStatement(Method method) {
  Class<?> parameterTypeClass = getParameterType(method);
  LanguageDriver languageDriver = getLanguageDriver(method);
  SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
  if (sqlSource != null) {
    Options options = method.getAnnotation(Options.class);
    final String mappedStatementId = type.getName() + "." + method.getName(); // 这里!!!
    Integer fetchSize = null;
    Integer timeout = null;
    StatementType statementType = StatementType.PREPARED;
    ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
    SqlCommandType sqlCommandType = getSqlCommandType(method);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = !isSelect;
    boolean useCache = isSelect;

    KeyGenerator keyGenerator;
    String keyProperty = "id";
    String keyColumn = null;
    if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
      // first check for SelectKey annotation - that overrides everything else
      SelectKey selectKey = method.getAnnotation(SelectKey.class);
      if (selectKey != null) {
        keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
        keyProperty = selectKey.keyProperty();
      } else if (options == null) {
        keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      } else {
        keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        keyProperty = options.keyProperty();
        keyColumn = options.keyColumn();
      }
    } else {
      keyGenerator = NoKeyGenerator.INSTANCE;
    }

    if (options != null) {
      if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
        flushCache = true;
      } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
        flushCache = false;
      }
      useCache = options.useCache();
      fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
      timeout = options.timeout() > -1 ? options.timeout() : null;
      statementType = options.statementType();
      resultSetType = options.resultSetType();
    }

    String resultMapId = null;
    ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
    if (resultMapAnnotation != null) {
      String[] resultMaps = resultMapAnnotation.value();
      StringBuilder sb = new StringBuilder();
      for (String resultMap : resultMaps) {
        if (sb.length() > 0) {
          sb.append(",");
        }
        sb.append(resultMap);
      }
      resultMapId = sb.toString();
    } else if (isSelect) {
      resultMapId = parseResultMap(method);
    }

    assistant.addMappedStatement(
        mappedStatementId, // 这里!!!
        sqlSource,
        statementType,
        sqlCommandType,
        fetchSize,
        timeout,
        // ParameterMapID
        null,
        parameterTypeClass,
        resultMapId,
        getReturnType(method),
        resultSetType,
        flushCache,
        useCache,
        // TODO gcode issue #577
        false,
        keyGenerator,
        keyProperty,
        keyColumn,
        // DatabaseID
        null,
        languageDriver,
        // ResultSets
        options != null ? nullOrEmpty(options.resultSets()) : null);
  }
}

assistant.addMappedStatement这里总算看到生成MappedStatement对象的地方了,继续跟进到MapperBuilderAssistant。addMappedStatement方法。

public MappedStatement addMappedStatement(
    String id,
    SqlSource sqlSource,
    StatementType statementType,
    SqlCommandType sqlCommandType,
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {

  if (unresolvedCacheRef) {
    throw new IncompleteElementException("Cache-ref not yet resolved");
  }

  id = applyCurrentNamespace(id, false);
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

  MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
      .resource(resource)
      .fetchSize(fetchSize)
      .timeout(timeout)
      .statementType(statementType)
      .keyGenerator(keyGenerator)
      .keyProperty(keyProperty)
      .keyColumn(keyColumn)
      .databaseId(databaseId)
      .lang(lang)
      .resultOrdered(resultOrdered)
      .resultSets(resultSets)
      .resultMaps(getStatementResultMaps(resultMap, resultType, id))
      .resultSetType(resultSetType)
      .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
      .useCache(valueOrDefault(useCache, isSelect))
      .cache(currentCache);

  ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  if (statementParameterMap != null) {
    statementBuilder.parameterMap(statementParameterMap);
  }

  MappedStatement statement = statementBuilder.build();
  configuration.addMappedStatement(statement);
  return statement;
}

public static class Builder {
  private MappedStatement mappedStatement = new MappedStatement();

  public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
    mappedStatement.configuration = configuration;
    mappedStatement.id = id;
    mappedStatement.sqlSource = sqlSource;
    mappedStatement.statementType = StatementType.PREPARED;
    mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
    mappedStatement.resultMaps = new ArrayList<ResultMap>();
    mappedStatement.sqlCommandType = sqlCommandType;
    mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    String logId = id;
    if (configuration.getLogPrefix() != null) {
      logId = configuration.getLogPrefix() + id;
    }
    mappedStatement.statementLog = LogFactory.getLog(logId); // 这里!!!
    mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
  }

总算找到了mappedStatement.statementLog = LogFactory.getLog(logId),回顾之前的,可以得出:

logId = type.getName() + "." + method.getName();
if (configuration.getLogPrefix() != null) {
  logId = configuration.getLogPrefix() + id;
}

那么typemethod分别又是什么值呢?
现在我们继续深入跟踪下,看什么时候解析这些内容。通过debug可以看到,SqlSessionFactiryBean(本项目是Spring项目)里buildSqlSessionFactory方法有循环解析的代码:

if (!isEmpty(this.mapperLocations)) {
  for (Resource mapperLocation : this.mapperLocations) {
    if (mapperLocation == null) {
      continue;
    }

    try {
      XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
          configuration, mapperLocation.toString(), configuration.getSqlFragments());
      xmlMapperBuilder.parse();
    } catch (Exception e) {
      throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
    } finally {
      ErrorContext.instance().reset();
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
    }
  }
} 

mapperLocation就是我们的mapper xml文件:
38949855.png
继续根据到XMLMapperBuilder.parse方法

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

private void bindMapperForNamespace() {
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      //ignore, bound type is not required
    }
    if (boundType != null) {
      if (!configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }
}

boundType的值就是XML文件里配置的namespace值。继续一步步根据:

Configuration.java 注册mapper
public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
}

MapperRegistry.java:解析指定类
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(); J
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

MapperAnnotationBuilder.java:解析type和method
public void parse() {
  String resource = type.toString();
  if (!configuration.isResourceLoaded(resource)) {
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    parseCache();
    parseCacheRef();
    Method[] methods = type.getMethods();
    for (Method method : methods) {
      try {
        // issue #237
        if (!method.isBridge()) {
          parseStatement(method);
        }
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

可以看到type就是Mapper类。method就是type下的方法。以下是项目debug的内容截图:
41893406.png


noname
314 声望49 粉丝

一只菜狗


下一篇 »
log4j2匹配规则