天天看點

mybatis源碼學習------sql語句的解析buildStatementFromContext方法

解析insert、update、delete和select配置的代碼入口如下所示:

mybatis源碼學習------sql語句的解析buildStatementFromContext方法

buildStatementFromContext方法

邏輯同解析sql片段相似,都是先檢查資料庫廠商ID是否比對

private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}
           

通過為每一個語句建立一個XMLStatementBuilder對象來解析使用者編寫的sql語句

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  //周遊所有的CRUD标簽,并逐個解析
  for (XNode context : list) {
    //建構XMLStatementBuilder執行個體
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      //解析配置
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}
           

XMLStatementBuilder#parseStatementNode方法

從功能上講,parseStatementNode方法主要做了三件事:

  • 處理

    <include></include>

    标簽
  • 處理

    <selectKey></selectKey>

    标簽
  • 建構sql語句對應的MappedStatement對象
public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");
  //如果資料庫廠商ID不比對,則直接傳回
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }
  //通過标簽名稱确定sql語句的類型
  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);
  //這個設定僅針對嵌套結果select語句
  //如果為true将會假設包含了嵌套結果集或是分組,當傳回一個主結果行時,就不會産生對前面結果集的引用。這就使得在擷取嵌套結果集的時候不至于記憶體不夠用。
  //預設值:false。
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  //處理<include></include>标簽
  includeParser.applyIncludes(context.getNode());

  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  //處理<selectKey></selectKey>
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  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;
  }

  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");

  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
           

1、處理include标簽

入口代碼如下,這裡會建立一個XMLIncludeTransformer的執行個體來解析需要引入的sql片段。該過程會将

<include>

節點替換成

<sql>

節點中定義的sql片段,并将其中的${xxx}占位符替換成真實值。

mybatis源碼學習------sql語句的解析buildStatementFromContext方法

applyIncludes方法

//先擷取全局的配置變量,也就是mybatis-config.xml配置檔案中的<properties></properties>下配置的變量。
//然後再調用重載方法進行處理
public void applyIncludes(Node source) {
  Properties variablesContext = new Properties();
  Properties configurationVariables = configuration.getVariables();
  Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll);
  applyIncludes(source, variablesContext, false);
}
           

該方法中涉及到多層遞歸,是以在此處通過一個例子和示意圖進行分析,代碼邏輯不複雜,隻是繞,參照下面例子多調試幾次就ok。

//遞歸處理include标簽中的内容
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
  if (source.getNodeName().equals("include")) {
    //查找refid對應的sql片段,并傳回sql片段對象的深拷貝
    Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
    //合并include中配置的局部變量和mybatis配置檔案中生命的全局變量
    Properties toIncludeContext = getVariablesContext(source, variablesContext);
    applyIncludes(toInclude, toIncludeContext, true);
    if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
      toInclude = source.getOwnerDocument().importNode(toInclude, true);
    }
    //将include替換為sql
    source.getParentNode().replaceChild(toInclude, source);
    while (toInclude.hasChildNodes()) {
      toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
    }
    //移除include标簽
    toInclude.getParentNode().removeChild(toInclude);
  } else if (source.getNodeType() == Node.ELEMENT_NODE) {
    if (included && !variablesContext.isEmpty()) {//替換<sql></sql>标簽屬性的占位符
      //用真實值替換節點屬性中的占位符
      NamedNodeMap attributes = source.getAttributes();
      for (int i = 0; i < attributes.getLength(); i++) {
        Node attr = attributes.item(i);
        attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
      }
    }
    //遞歸處理标簽下所有子标簽的占位符
    NodeList children = source.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      applyIncludes(children.item(i), variablesContext, included);
    }
  } else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE)
      && !variablesContext.isEmpty()) {
    //用真實值替換文本或CDATA區中的占位符
    source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
  }
}
           
例子

本例中涉及的部分源碼會在後面詳細分析

sql片段和引用sql片段的查詢語句如下:

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
  from some_table t1
    cross join some_table t2
</select>
           

1、合并全局變量和局部變量

對應的代碼為XMLIncludeTransformer#getVariablesContext,該方法會将mybatis-config.xml配置檔案中的

<properties></properties>

下配置的變量和上面

<include></include>

内配置的變量進行合并,并儲存在一個Properties執行個體中

2、查找include所引用的sql片段

對應XMLIncludeTransformer#findSqlFragment方法,這裡會傳回sql片段的深克隆對象。因為一個sql片段會被多次引用,是以這裡傳回的一定是原始對象的拷貝。

3、替換sql節點中的所有占位符

在本例中,sql片段的深克隆對象會由原先的

替換為

4、處理select标簽内的include标簽

​ 4.1首先會将select标簽下的include标簽替換為sql标簽,此時select的樣子如下:

<select id="selectUsers" resultType="map">
  select
    <sql id="userColumns"> t1.id,t1.username,t1.password </sql>,
  from some_table t1
    cross join some_table t2
</select>
           

​ 4.2 将sql中的内容插入到select節點中對應的位置,此時此時select的樣子如下:

<select id="selectUsers" resultType="map">
  select
    t1.id,t1.username,t1.password
    <sql id="userColumns"> t1.id,t1.username,t1.password </sql>,
  from some_table t1
    cross join some_table t2
</select>
           

​ 4.3.移除多餘的sql節點,此時此時select的樣子如下:

<select id="selectUsers" resultType="map">
  select
    t1.id,t1.username,t1.password,
  from some_table t1
    cross join some_table t2
</select>
           
XMLIncludeTransformer#findSqlFragment
private Node findSqlFragment(String refid, Properties variables) {
    //如果refid中存在占位符,則使用真實值将其替換
    refid = PropertyParser.parse(refid, variables);
    //拼接namespace
    refid = builderAssistant.applyCurrentNamespace(refid, true);
    try {
      XNode nodeToInclude = configuration.getSqlFragments().get(refid);
      //傳回對象的深拷貝
      return nodeToInclude.getNode().cloneNode(true);
    } catch (IllegalArgumentException e) {
      throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);
    }
  }
           
XMLIncludeTransformer#getVariablesContext
private Properties getVariablesContext(Node node, Properties inheritedVariablesContext) {
  Map<String, String> declaredProperties = null;
  NodeList children = node.getChildNodes();
  for (int i = 0; i < children.getLength(); i++) {
    Node n = children.item(i);
    if (n.getNodeType() == Node.ELEMENT_NODE) {
      String name = getStringAttribute(n, "name");
      //解析并用實際值替換占位符
      String value = PropertyParser.parse(getStringAttribute(n, "value"), inheritedVariablesContext);
      if (declaredProperties == null) {
        declaredProperties = new HashMap<>();
      }
      if (declaredProperties.put(name, value) != null) {
        throw new BuilderException("Variable " + name + " defined twice in the same include definition");
      }
    }
  }
  //執行合并
  if (declaredProperties == null) {
    return inheritedVariablesContext;
  } else {//合并屬性并傳回
    Properties newProperties = new Properties();
    newProperties.putAll(inheritedVariablesContext);
    newProperties.putAll(declaredProperties);
    return newProperties;
  }
}
           

2、處理selectKey标簽

對于不支援自動生成主鍵列的資料庫和可能不支援自動生成主鍵的 JDBC 驅動,MyBatis 提供了selectKey來生成主鍵。

例如

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>
           

selectKey 元素的屬性如下:

屬性 描述

keyProperty

selectKey

語句結果應該被設定到的目标屬性。如果生成列不止一個,可以用逗号分隔多個屬性名稱。

keyColumn

傳回結果集中生成列屬性的列名。如果生成列不止一個,可以用逗号分隔多個屬性名稱。

resultType

結果的類型。通常 MyBatis 可以推斷出來,但是為了更加準确,寫上也不會有什麼問題。MyBatis 允許将任何簡單類型用作主鍵的類型,包括字元串。如果生成列不止一個,則可以使用包含期望屬性的 Object 或 Map。

order

可以設定為

BEFORE

AFTER

。如果設定為

BEFORE

,那麼它首先會生成主鍵,設定

keyProperty

再執行插入語句。如果設定為

AFTER

,那麼先執行插入語句,然後是

selectKey

中的語句 - 這和 Oracle 資料庫的行為相似,在插入語句内部可能有嵌入索引調用。

statementType

和前面一樣,MyBatis 支援

STATEMENT

PREPARED

CALLABLE

類型的映射語句,分别代表

Statement

,

PreparedStatement

CallableStatement

類型。

處理selectKey标簽配置的入口為:

private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
  List<XNode> selectKeyNodes = context.evalNodes("selectKey");
  if (configuration.getDatabaseId() != null) {
    //調用重載方法  
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
  }
  //調用重載方法  
  parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
  removeSelectKeyNodes(selectKeyNodes);
}
           
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
  for (XNode nodeToHandle : list) {
    //parentId 為外層sql語句的id屬性的值
    String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    String databaseId = nodeToHandle.getStringAttribute("databaseId");
    if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
      parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
    }
  }
}
           

parseSelectKeyNode

實際解析

<select></select>

标簽的方法,這裡會将

<select></select>

标簽内定義的sql封裝為一個MappedStatement對象,并儲存在configuration對象中,友善後續調用sql時擷取并執行。

其中建立sqlSource對象的邏輯這裡不展開,詳細資訊可以檢視另外兩篇文章,[mybatis源碼學習------動态sql的解析(SqlNode)](https://blog.csdn.net/qq_35835624/article/details/109457127)

mybatis源碼學習------動态sql的解析(SqlSource)

private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
  //擷取使用者配置的值   ---start
  String resultType = nodeToHandle.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
  String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
  boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
  //擷取使用者配置的值   ---end
  // defaults
  boolean useCache = false;
  boolean resultOrdered = false;
  KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
  Integer fetchSize = null;
  Integer timeout = null;
  boolean flushCache = false;
  String parameterMap = null;
  String resultMap = null;
  ResultSetType resultSetTypeEnum = null;
  //建立<selectKey></selectKey>标簽内定義的sql的對象sqlSource
  //建立sqlSource對象的邏輯這裡不展開,詳細資訊可以檢視另外兩篇文章
  //https://blog.csdn.net/qq_35835624/article/details/109457127
  //https://blog.csdn.net/qq_35835624/article/details/109591038
  SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
  SqlCommandType sqlCommandType = SqlCommandType.SELECT;
  //将<selectKey></selectKey>标簽内定義的sql封裝為一個MappedStatement對象
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
  //給id拼接命名空間
  id = builderAssistant.applyCurrentNamespace(id, false);

  MappedStatement keyStatement = configuration.getMappedStatement(id, false);
  //添加到configuration對象中
  configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}
           

removeSelectKeyNodes

移除節點中的selectKey配置,例如,移除前為:

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>
           

移除後

<insert id="insertAuthor">
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>
           

3、處理sql語句配置

在處理完include标簽和selectKey标簽後,接下來将處理sql語句的配置,關于sql語句的配置,mybatis将其分為兩大類,即查詢類sql和修改類sql。查詢類就是

<select></select>

的配置,而修改類則包括了

<insert></insert>

<update></update>

<delete></delete>

查詢類sql的配置

屬性 描述

id

在命名空間中唯一的辨別符,可以被用來引用這條語句。

parameterType

将會傳入這條語句的參數的類全限定名或别名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器(TypeHandler)推斷出具體傳入語句的參數,預設值為未設定(unset)。
parameterMap 用于引用外部 parameterMap 的屬性,目前已被廢棄。請使用行内參數映射和 parameterType 屬性。

resultType

期望從這條語句中傳回結果的類全限定名或别名。 注意,如果傳回的是集合,那應該設定為集合包含的類型,而不是集合本身的類型。 resultType 和 resultMap 之間隻能同時使用一個。

resultMap

對外部 resultMap 的命名引用。結果映射是 MyBatis 最強大的特性,如果你對其了解透徹,許多複雜的映射問題都能迎刃而解。 resultType 和 resultMap 之間隻能同時使用一個。

flushCache

将其設定為 true 後,隻要語句被調用,都會導緻本地緩存和二級緩存被清空,預設值:false。

useCache

将其設定為 true 後,将會導緻本條語句的結果被二級緩存緩存起來,預設值:對 select 元素為 true。

timeout

這個設定是在抛出異常之前,驅動程式等待資料庫傳回請求結果的秒數。預設值為未設定(unset)(依賴資料庫驅動)。

fetchSize

這是一個給驅動的建議值,嘗試讓驅動程式每次批量傳回的結果行數等于這個設定值。 預設值為未設定(unset)(依賴驅動)。

statementType

可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。

resultSetType

FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等價于 unset) 中的一個,預設值為 unset (依賴資料庫驅動)。

databaseId

如果配置了資料庫廠商辨別(databaseIdProvider),MyBatis 會加載所有不帶 databaseId 或比對目前 databaseId 的語句;如果帶和不帶的語句都有,則不帶的會被忽略。

resultOrdered

這個設定僅針對嵌套結果 select 語句:如果為 true,将會假設包含了嵌套結果集或是分組,當傳回一個主結果行時,就不會産生對前面結果集的引用。 這就使得在擷取嵌套結果集的時候不至于記憶體不夠用。預設值:

false

resultSets

這個設定僅适用于多結果集的情況。它将列出語句執行後傳回的結果集并賦予每個結果集一個名稱,多個名稱之間以逗号分隔。

例如:

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
           

修改類sql的配置

id

在命名空間中唯一的辨別符,可以被用來引用這條語句。

parameterType

将會傳入這條語句的參數的類全限定名或别名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器(TypeHandler)推斷出具體傳入語句的參數,預設值為未設定(unset)。

parameterMap

用于引用外部 parameterMap 的屬性,目前已被廢棄。請使用行内參數映射和 parameterType 屬性。

flushCache

将其設定為 true 後,隻要語句被調用,都會導緻本地緩存和二級緩存被清空,預設值:(對 insert、update 和 delete 語句)true。

timeout

這個設定是在抛出異常之前,驅動程式等待資料庫傳回請求結果的秒數。預設值為未設定(unset)(依賴資料庫驅動)。

statementType

可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。

useGeneratedKeys

(僅适用于 insert 和 update)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由資料庫内部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關系型資料庫管理系統的自動遞增字段),預設值:false。

keyProperty

(僅适用于 insert 和 update)指定能夠唯一識别對象的屬性,MyBatis 會使用 getGeneratedKeys 的傳回值或 insert 語句的 selectKey 子元素設定它的值,預設值:未設定(

unset

)。如果生成列不止一個,可以用逗号分隔多個屬性名稱。

keyColumn

(僅适用于 insert 和 update)設定生成鍵值在表中的列名,在某些資料庫(像 PostgreSQL)中,當主鍵列不是表中的第一列的時候,是必須設定的。如果生成列不止一個,可以用逗号分隔多個屬性名稱。

databaseId

如果配置了資料庫廠商辨別(databaseIdProvider),MyBatis 會加載所有不帶 databaseId 或比對目前 databaseId 的語句;如果帶和不帶的語句都有,則不帶的會被忽略。

例如:

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
           

XMLStatementBuilder#parseStatementNode方法的目的就是解析上述配置,并将使用者配置的資訊封裝到一個MappedStatement執行個體中

方法定義如下:

public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");
  //如果資料庫廠商ID不比對,則直接傳回
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }
  //通過标簽名稱确定sql語句的類型
  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);
  //這個設定僅針對嵌套結果select語句
  //如果為true将會假設包含了嵌套結果集或是分組,當傳回一個主結果行時,就不會産生對前面結果集的引用。這就使得在擷取嵌套結果集的時候不至于記憶體不夠用。
  //預設值:false。
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  //處理<include></include>标簽
  includeParser.applyIncludes(context.getNode());

  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  //處理<selectKey></selectKey>
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {//如果在configuration中沒找到對應的keyGenerator對象,則判斷使用者是否配置了useGeneratedKeys屬性,根據使用者配置使用不同的單例對象
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");
  //建立sql語句對應的MappedStatement對象
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
           

繼續閱讀