1. 程式人生 > >Mybatis總結(一)SqlSessionFactory初始化過程(原始碼分析)

Mybatis總結(一)SqlSessionFactory初始化過程(原始碼分析)

SqlSessionFactoryBuilder根據傳入的資料流生成Configuration物件,然後根據Configuration物件建立預設的SqlSessionFactory例項。

Mybatis初始化要經過簡單的以下幾步:

1. 呼叫SqlSessionFactoryBuilder物件的build(inputStream)方法;

2. SqlSessionFactoryBuilder會根據輸入流inputStream等資訊建立XMLConfigBuilder物件;

3. SqlSessionFactoryBuilder呼叫XMLConfigBuilder物件的parse()方法;

4. XMLConfigBuilder

物件返回Configuration物件;

5. SqlSessionFactoryBuilder根據Configuration物件建立一個DefaultSessionFactory物件;

6. SqlSessionFactoryBuilder返回 DefaultSessionFactory物件給Client,供Client使用。

一、SqlSessionFactory的建立:建造者模式:

SqlSessionFactoryBuilder相關的程式碼如下所示: 

public class SqlSessionFactoryBuilder {
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }
  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }
  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); // Don4j解析器
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }
  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {

//建立XMLConfigBuilder物件用來解析XML配置檔案,生成Configuration物件
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

//將XML配置檔案內的資訊解析成Java物件Configuration物件。根據Configuration物件創建出SqlSessionFactory物件
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
//內部通過Configuration物件來建立SqlSessionFactory,也可以自己通過API構造好Configuration物件,呼叫此方法建立SqlSessionFactory    
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}:

SqlSessionFactoryBuilder用於建立SqlSessionFactory。通過傳入的xml配置檔案,進行解析,儲存為內部的Configuration物件,最終呼叫build方法,new出一個DefaultSqlSessionFactory。

本過程設計的物件:

SqlSessionFactoryBuilder SqlSessionFactory的構造器,用於建立SqlSessionFactory,採用了Builder設計模式

Configuration :該物件是mybatis-config.xml檔案中所有mybatis配置資訊

SqlSessionFactorySqlSession

工廠類,以工廠形式建立SqlSession物件,採用了Factory工廠設計模式

XmlConfigParser :負責將mybatis-config.xml配置檔案解析成Configuration物件,共SqlSessonFactoryBuilder使用,建立SqlSessionFactory

二、解析xml配置為Configuration物件的過程:

這個過程比較複雜,主要是解析配置檔案,然後儲存到map中。

SqlSessionFactoryBuilder執行build()方法,呼叫了XMLConfigBuilderparse()方法,然後返回了Configuration物件

過程: XMLConfigBuilder會將XML配置檔案的資訊轉換為Document物件,而XML配置定義檔案DTD轉換成XMLMapperEntityResolver物件,然後將二者封裝到XpathParser物件中,XpathParser的作用是提供根據Xpath表示式獲取基本的DOM節點Node資訊的操作。

XMLConfigBuilder會將xml以及配置的mapper檔案轉化成對應的document物件,將配置定義檔案dtd載入成EntityResolve物件,組成XPathParser物件,然後根據XPath表示式訪問Node節點

XMLConfigBuilder呼叫parse()方法:從XPathParser中取出<configuration>節點對應的Node物件,然後解析此Node節點的子Nodeproperties, settings,typeAliases,typeHandlers, objectFactory, objectWrapperFactory, plugins, environments,databaseIdProvider,mappers

原始碼分析:

public Configuration parse() {

if (parsed) {
   throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));  //
  return configuration;

}


//解析 "/configuration"節點下的子節點資訊,然後將解析的結果設定到Configuration物件中


private void parseConfiguration(XNode root) {
try {
 propertiesElement(root.evalNode("properties"));//1.首先處理properties 節點
 Properties settings = settingsAsProperties(root.evalNode("settings")); 
 loadCustomVfs(settings);
 typeAliasesElement(root.evalNode("typeAliases"));//2.處理typeAliases
 pluginElement(root.evalNode("plugins"));     //3.處理外掛
 objectFactoryElement(root.evalNode("objectFactory"));
 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
 reflectorFactoryElement(root.evalNode("reflectorFactory"));
 settingsElement(settings);
 // read it after objectFactory and objectWrapperFactory issue #631
 environmentsElement(root.evalNode("environments"));
 databaseIdProviderElement(root.evalNode("databaseIdProvider"));
 typeHandlerElement(root.evalNode("typeHandlers"));
 mapperElement(root.evalNode("mappers")); // 這裡解析出SQL。其實也就是mapper檔案或mapper註解
} catch (Exception e) {
 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

解析mapper的過程:

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {  // 遍歷
      if ("package".equals(child.getName())) {   // 
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);  // 這裡添加了mappers
      } else {
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

這裡的Configuration物件在迴圈中add了mapper

Configuration

public class Configuration {
protected Environment environment;

。。。。

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

public void addMappers(String packageName, Class<?> superType) {

  mapperRegistry.addMappers(packageName, superType);

}
public void addMappers(String packageName) {
  mapperRegistry.addMappers(packageName);
}

可以看到在Configuration中,使用mapperRegistry進行新增

MapperRegistry

public void addMappers(String packageName) {
  addMappers(packageName, Object.class);
}

public void addMappers(String packageName, Class<?> superType) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    addMapper(mapperClass);
  }
}

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

MapperRegistry呼叫用了MapperAnnotationBuilder進行解析

MapperAnnotationBuilder

MapperAnnotationBuilder:
public class MapperAnnotationBuilder {
  private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
  private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();
  private Configuration configuration;
  private MapperBuilderAssistant assistant;
  private Class<?> type;
// 這裡將註解的情況加入進去,添加註解的,
  public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
    String resource = type.getName().replace('.', '/') + ".java (best guess)";
    this.assistant = new MapperBuilderAssistant(configuration, resource);
    this.configuration = configuration;
    this.type = type;   // 這個type是介面
    sqlAnnotationTypes.add(Select.class);
    sqlAnnotationTypes.add(Insert.class);
    sqlAnnotationTypes.add(Update.class);
    sqlAnnotationTypes.add(Delete.class);
    sqlProviderAnnotationTypes.add(SelectProvider.class);
    sqlProviderAnnotationTypes.add(InsertProvider.class);
    sqlProviderAnnotationTypes.add(UpdateProvider.class);
    sqlProviderAnnotationTypes.add(DeleteProvider.class);
  }
  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();  // 首先這裡是先載入xml。如果發現沒有xml,不丟擲異常,這裡是忽略這個異常
      configuration.addLoadedResource(resource);  // 把載入好的資源放在loadedResources這個set容器中,表示已經載入
      assistant.setCurrentNamespace(type.getName());  // 設定當前的namespace為當前類名
      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();
  }

這個過程先是解析了載入了xml。這裡涉及特別精巧,在載入xml的過程中,如果沒有xml,那麼xml解析器會丟擲異常,而mybatis將丟擲的異常進行捕獲,然後忽略了這個異常,等下一個解析註解。

 private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";  // 這裡就是預設的xml路徑了,如果沒有配置,那麼預設需要同包下的xml
      InputStream inputStream = null;
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {  // 如果沒有,則忽略這個異常
        // ignore, resource is not required
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

如果有xml,則使用XMLMapperBuilder 進行解析xml。後面討論

回到解析方法:

 public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();  // 首先這裡是先載入xml。如果發現沒有xml,不丟擲異常,這裡是忽略這個異常
      configuration.addLoadedResource(resource);  // 把載入好的資源放在loadedResources這個set容器中,表示已經載入
      assistant.setCurrentNamespace(type.getName());  // 設定當前的namespace為當前類名
      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();
  }

載入xml後,會再次去遍歷註解,如果xml和註解同時配置,根據程式碼先解析xml,後解析註解,可以得知,註解是會覆蓋xml的同名方法的。

接下來會對方法引數進行解析。

// 對方法的引數進行解析
void parseStatement(Method method) {
  Class<?> parameterTypeClass = getParameterType(method);
  LanguageDriver languageDriver = getLanguageDriver(method);
  SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);  //這裡會對方法以及引數進行解析,最終封裝為一個很重要的類-SqlSource
  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);
  }
}

SqlSource的獲取:

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
  try {
    Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
    Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
    if (sqlAnnotationType != null) {
      if (sqlProviderAnnotationType != null) {
        throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
      }
      Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
      final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
      return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
    } else if (sqlProviderAnnotationType != null) {
      Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
      return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
    }
    return null;
  } catch (Exception e) {
    throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
  }
}

ProviderSqlSource類是SqlSource的子類。儲存了封裝的SQL資訊

public class ProviderSqlSource implements SqlSource {
public ProviderSqlSource(Configuration config, Object provider) {
  String providerMethodName;
  try {
    this.sqlSourceParser = new SqlSourceBuilder(config);
    this.providerType = (Class<?>) provider.getClass().getMethod("type").invoke(provider);
    providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider);

    for (Method m : this.providerType.getMethods()) {
      if (providerMethodName.equals(m.getName())) {
        if (m.getReturnType() == String.class) {
          if (providerMethod != null){
            throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
                    + providerMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName()
                    + "'. Sql provider method can not overload.");
          }
          this.providerMethod = m;
          this.providerMethodArgumentNames = new ParamNameResolver(config, m).getNames();  // 引數名
        }
      }
    }
  } catch (BuilderException e) {
    throw e;
  } catch (Exception e) {
    throw new BuilderException("Error creating SqlSource for SqlProvider.  Cause: " + e, e);
  }
  if (this.providerMethod == null) {
    throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
        + providerMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'.");
  }
}

ParamNameResolver是獲取方法引數名稱的類:

public class ParamNameResolver {
public ParamNameResolver(Configuration config, Method method) {
  final Class<?>[] paramTypes = method.getParameterTypes();  // 這裡會先獲取方法的引數型別
  final Annotation[][] paramAnnotations = method.getParameterAnnotations();
  final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
  int paramCount = paramAnnotations.length;
  // get names from @Param annotations
  for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
    if (isSpecialParameter(paramTypes[paramIndex])) {  // 跳過特殊的引數型別,也就是判斷引數的類是否是RowBounds或者ResultHandler,如果是,就跳過
      // skip special parameters
      continue;
    }
    String name = null;
    for (Annotation annotation : paramAnnotations[paramIndex]) {
      if (annotation instanceof Param) {   // 這裡解析了Param
        hasParamAnnotation = true;
        name = ((Param) annotation).value();
        break;
      }
    }
    if (name == null) {   // 沒有@Param註解
      // @Param was not specified.
      if (config.isUseActualParamName()) {
        name = getActualParamName(method, paramIndex);  // 這裡會獲取到真實的引數名或者是arg+索引值
      }
      if (name == null) {    //如果還是沒有取到值
        // use the parameter index as the name ("0", "1", ...)
        // gcode issue #71
        name = String.valueOf(map.size());
      }
    }
    map.put(paramIndex, name);  // 引數索引 + 引數名
  }
  names = Collections.unmodifiableSortedMap(map);
}

可以看到,這裡會先判斷方法引數是否有@Param註解,如果有,則獲取值,然後儲存到map中,如果沒有,判斷isUseActualParamName。如果判斷成功,會獲取真實引數名或者是arg+索引值。如果不是,則命名預設為索引值。

引數的解析就到這裡一段落。接下來是之前沒有解決的xml解析的問題。

動態SQL是如何解析出來的?

XMLMapperBuilderparse方法:

public void parse() {

  if (!configuration.isResourceLoaded(resource)) {

    configurationElement(parser.evalNode("/mapper"));

    configuration.addLoadedResource(resource);

    bindMapperForNamespace();

  }

  parsePendingResultMaps();

  parsePendingCacheRefs();

  parsePendingStatements();

}
private void configurationElement(XNode context) {

  try {

    String namespace = context.getStringAttribute("namespace");

    if (namespace == null || namespace.equals("")) {

      throw new BuilderException("Mapper's namespace cannot be empty");

    }

    builderAssistant.setCurrentNamespace(namespace);

    cacheRefElement(context.evalNode("cache-ref"));

    cacheElement(context.evalNode("cache"));

    parameterMapElement(context.evalNodes("/mapper/parameterMap"));

    resultMapElements(context.evalNodes("/mapper/resultMap"));

    sqlElement(context.evalNodes("/mapper/sql"));

    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

  } catch (Exception e) {

    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);

  }

}
private void buildStatementFromContext(List<XNode> list) {

  if (configuration.getDatabaseId() != null) {

    buildStatementFromContext(list, configuration.getDatabaseId());

  }

  buildStatementFromContext(list, null);

}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {

  for (XNode context : list) {

    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);

    try {

      statementParser.parseStatementNode(); 

    } catch (IncompleteElementException e) {

      configuration.addIncompleteStatement(statementParser);

    }

  }

}

最終是使用 XMLStatementBuilder進行解析元素

XMLStatementBuilder

public class XMLStatementBuilder extends BaseBuilder {
public void parseStatementNode() {

  String id = context.getStringAttribute("id");

  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return;  }

  Integer fetchSize = context.getIntAttribute("fetchSize");

  Integer timeout = context.getIntAttribute("timeout");

  String parameterMap = context.getStringAttribute("parameterMap");

  String parameterType = context.getStringAttribute("parameterType");

  Class<?> parameterTypeClass = resolveClass(parameterType);  // 對傳入的引數進行型別解析[其實就是看是否有別名]

  String resultMap = context.getStringAttribute("resultMap");

  String resultType = context.getStringAttribute("resultType");

  String lang = context.getStringAttribute("lang");

  LanguageDriver langDriver = getLanguageDriver(lang);

  Class<?> resultTypeClass = resolveClass(resultType);

  String resultSetType = context.getStringAttribute("resultSetType");

  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));  // 預設是使用PreparedStatement,如果配置了,就使用配置的

  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

  String nodeName = context.getNode().getNodeName();

  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);

  boolean useCache = context.getBooleanAttribute("useCache", isSelect);

  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing

  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);

  includeParser.applyIncludes(context.getNode());

  // Parse selectKey after includes and remove them.

  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 這裡解析了SQL

  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); // 這裡建立了sql

  String resultSets = context.getStringAttribute("resultSets");

  String keyProperty = context.getStringAttribute("keyProperty");

  String keyColumn = context.getStringAttribute("keyColumn");

  KeyGenerator keyGenerator;

  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;

  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);

  if (configuration.hasKeyGenerator(keyStatementId)) {

    keyGenerator = configuration.getKeyGenerator(keyStatementId);

  } else {

    keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;

  }

  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,

      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,

      resultSetTypeEnum, flushCache, useCache, resultOrdered, 

      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

}

首先,可以看到它從上下文中獲取了配置檔案配置的資訊。根據這些資訊決定建立sql的策略。

如StatementType如果沒有配置的話,預設就是PreparedStatement。

關鍵點在於langDriver的createSqlSource方法。這裡建立了SqlSource

LanguageDriver是一個就介面,xmlLanguageDriver是其實現類

public interface LanguageDriver {
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql); 
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType); 
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
看一下XMLLanguageDriver是如何實現的
public class XMLLanguageDriver implements LanguageDriver {
  @Override
  public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
  }
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }
  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    // issue #3
    if (script.startsWith("<script>")) {
      XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
      // issue #127
      script = PropertyParser.parse(script, configuration.getVariables());
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) {
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
        return new RawSqlSource(configuration, script, parameterType);
      }
    }
  }
}
之前呼叫的是其第二個方法,可以看到,這裡又使用了建造模式,又是使用XMLScriptBuilder進行構建SqlSource

XMLScriptBuilder是SqlSource建立的關鍵類。

// 將xml中的sql封裝為動態的SqlSource,最後會解析成靜態的
public SqlSource parseScriptNode() {
  List<SqlNode> contents = parseDynamicTags(context);
  MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
  SqlSource sqlSource = null;
  if (isDynamic) {
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);  // 建立動態的sql
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);  // 建立原始的sql
  }
  return sqlSource;
}
List<SqlNode> parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<SqlNode>();
  NodeList children = node.getNode().getChildNodes();
  for (int i = 0; i < children.getLength(); i++) {
    XNode child = node.newXNode(children.item(i));
    if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
      String data = child.getStringBody("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
      String nodeName = child.getNode().getNodeName();
      NodeHandler handler = nodeHandlers(nodeName);
      if (handler == null) {
        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
      }
      handler.handleNode(child, contents);
      isDynamic = true;
    }
  }
  return contents;
}
NodeHandler nodeHandlers(String nodeName) {
  Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
  map.put("trim", new TrimHandler());
  map.put("where", new WhereHandler());
  map.put("set", new SetHandler());
  map.put("foreach", new ForEachHandler());
  map.put("if", new IfHandler());
  map.put("choose", new ChooseHandler());
  map.put("when", new IfHandler());
  map.put("otherwise", new OtherwiseHandler());
  map.put("bind", new BindHandler());
  return map.get(nodeName);
}

這裡進行了動態SQL的解析,可以看到nodeHandlers方法,對不同的元素,進行不同的Handler進行解析。

最終根據判斷isDynamic判斷是否建立動態的SQL或靜態的。最後返回這個SqlSource。

對於SqlSource的解析:

SqlSource是獲取了,但是還需要經過解析,

以動態的SqlSource為例子

public class DynamicSqlSource implements SqlSource {

  private Configuration configuration;
  private SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }
  // 在會話建立時,會被呼叫,然後會生成一個靜態的sql,帶?引數的【也就是去掉引數】
  @Override
  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();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

}

sqlSourceBuilder

public class SqlSourceBuilder extends BaseBuilder {
private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
  super(configuration);
}
public SqlSource parse(String originalSql,Class<?> parameterType,Map<String,Object> additionalParameters) {
  ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  String sql = parser.parse(originalSql);
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
GenericTokenParser 
public class GenericTokenParser {
public String parse(String text) {
  if (text == null || text.isEmpty()) {  return "";  }
  char[] src = text.toCharArray();
  int offset = 0;
  // search open token
  int start = text.indexOf(openToken, offset);
  if (start == -1) {    return text;  }
  final StringBuilder builder = new StringBuilder();
  StringBuilder expression = null;
  while (start > -1) {
    if (start > 0 && src[start - 1] == '\\') {
      // this open token is escaped. remove the backslash and continue.
      builder.append(src, offset, start - offset - 1).append(openToken);
      offset = start + openToken.length();
    } else {
      // found open token. let's search close token.
      if (expression == null) {   expression = new StringBuilder(); } 
else {    expression.setLength(0);   }
      builder.append(src, offset, start - offset);
      offset = start + openToken.length();
      int end = text.indexOf(closeToken, offset);
      while (end > -1) {
        if (end > offset && src[end - 1] == '\\') {
          // this close token is escaped. remove the backslash and continue.
          expression.append(src, offset, end - offset - 1).append(closeToken);
          offset = end + closeToken.length();
          end = text.indexOf(closeToken, offset);
        } else {
          expression.append(src, offset, end - offset);
          offset = end + closeToken.length();
          break;
        }
      }
      if (end == -1) {
        // close token was not found.
        builder.append(src, start, src.length - start);
        offset = src.length;
      } else {
        builder.append(handler.handleToken(expression.toString()));  // 新增?
        offset = end + closeToken.length();
      }
    }
    start = text.indexOf(openToken, offset);
  }
  if (offset < src.length) {
    builder.append(src, offset, src.length - offset);
  }
  return builder.toString();
}

這裡做了很多很複雜的事情,但是handler的handlerToken就是將使用者在SQL裡面寫的#{}替換成?,最終由PreparedStatement編織最終傳給資料庫的SQL。

SqlSourceBuilder 

public class SqlSourceBuilder extends BaseBuilder {
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
@Override
public String handleToken(String content) {
  parameterMappings.add(buildParameterMapping(content));
  return "?";
}
private ParameterMapping buildParameterMapping(String content) {
  Map<String, String> propertiesMap = parseParameterMapping(content);
  String property = propertiesMap.get("property");
  Class<?> propertyType;
  if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
    propertyType = metaParameters.getGetterType(property);
  } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
    propertyType = parameterType;
  } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
    propertyType = java.sql.ResultSet.class;
  } else if (property != null) {
    MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
    if (metaClass.hasGetter(property)) {
      propertyType = metaClass.getGetterType(property);
    } else {
      propertyType = Object.class;
    }
  } else {
    propertyType = Object.class;
  }
  ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
  Class<?> javaType = propertyType;
  String typeHandlerAlias = null;
  for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
    String name = entry.getKey();
    String value = entry.getValue();
    if ("javaType".equals(name)) {
      javaType = resolveClass(value);
      builder.javaType(javaType);
    } else if ("jdbcType".equals(name)) {
      builder.jdbcType(resolveJdbcType(value));
    } else if ("mode".equals(name)) {
      builder.mode(resolveParameterMode(value));
    } else if ("numericScale".equals(name)) {
      builder.numericScale(Integer.valueOf(value));
    } else if ("resultMap".equals(name)) {
      builder.resultMapId(value);
    } else if ("typeHandler".equals(name)) {
      typeHandlerAlias = value;
    } else if ("jdbcTypeName".equals(name)) {
      builder.jdbcTypeName(value);
    } else if ("property".equals(name)) {
      // Do Nothing
    } else if ("expression".equals(name)) {
      throw new BuilderException("Expression based parameters are not supported yet");
    } else {
      throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);
    }
  }
  if (typeHandlerAlias != null) {
    builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
  }
  return builder.build();
}
private Map<String, String> parseParameterMapping(String content) {
  try {
    return new ParameterExpression(content);
  } catch (BuilderException ex) {
    throw ex;
  } catch (Exception ex) {
    throw new BuilderException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
  }
}
}
}

在整個過程比較複雜,其實也就是不斷解析xml或註解的過程


流程圖