解析insert、update、delete和select配置的代碼入口如下所示:
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}占位符替換成真實值。
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 元素的屬性如下:
屬性 | 描述 |
---|---|
| 語句結果應該被設定到的目标屬性。如果生成列不止一個,可以用逗号分隔多個屬性名稱。 |
| 傳回結果集中生成列屬性的列名。如果生成列不止一個,可以用逗号分隔多個屬性名稱。 |
| 結果的類型。通常 MyBatis 可以推斷出來,但是為了更加準确,寫上也不會有什麼問題。MyBatis 允許将任何簡單類型用作主鍵的類型,包括字元串。如果生成列不止一個,則可以使用包含期望屬性的 Object 或 Map。 |
| 可以設定為 或 。如果設定為 ,那麼它首先會生成主鍵,設定 再執行插入語句。如果設定為 ,那麼先執行插入語句,然後是 中的語句 - 這和 Oracle 資料庫的行為相似,在插入語句内部可能有嵌入索引調用。 |
| 和前面一樣,MyBatis 支援 , 和 類型的映射語句,分别代表 , 和 類型。 |
處理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的配置
屬性 | 描述 |
---|---|
| 在命名空間中唯一的辨別符,可以被用來引用這條語句。 |
| 将會傳入這條語句的參數的類全限定名或别名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器(TypeHandler)推斷出具體傳入語句的參數,預設值為未設定(unset)。 |
parameterMap | 用于引用外部 parameterMap 的屬性,目前已被廢棄。請使用行内參數映射和 parameterType 屬性。 |
| 期望從這條語句中傳回結果的類全限定名或别名。 注意,如果傳回的是集合,那應該設定為集合包含的類型,而不是集合本身的類型。 resultType 和 resultMap 之間隻能同時使用一個。 |
| 對外部 resultMap 的命名引用。結果映射是 MyBatis 最強大的特性,如果你對其了解透徹,許多複雜的映射問題都能迎刃而解。 resultType 和 resultMap 之間隻能同時使用一個。 |
| 将其設定為 true 後,隻要語句被調用,都會導緻本地緩存和二級緩存被清空,預設值:false。 |
| 将其設定為 true 後,将會導緻本條語句的結果被二級緩存緩存起來,預設值:對 select 元素為 true。 |
| 這個設定是在抛出異常之前,驅動程式等待資料庫傳回請求結果的秒數。預設值為未設定(unset)(依賴資料庫驅動)。 |
| 這是一個給驅動的建議值,嘗試讓驅動程式每次批量傳回的結果行數等于這個設定值。 預設值為未設定(unset)(依賴驅動)。 |
| 可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。 |
| FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等價于 unset) 中的一個,預設值為 unset (依賴資料庫驅動)。 |
| 如果配置了資料庫廠商辨別(databaseIdProvider),MyBatis 會加載所有不帶 databaseId 或比對目前 databaseId 的語句;如果帶和不帶的語句都有,則不帶的會被忽略。 |
| 這個設定僅針對嵌套結果 select 語句:如果為 true,将會假設包含了嵌套結果集或是分組,當傳回一個主結果行時,就不會産生對前面結果集的引用。 這就使得在擷取嵌套結果集的時候不至于記憶體不夠用。預設值: 。 |
| 這個設定僅适用于多結果集的情況。它将列出語句執行後傳回的結果集并賦予每個結果集一個名稱,多個名稱之間以逗号分隔。 |
例如:
<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的配置
| 在命名空間中唯一的辨別符,可以被用來引用這條語句。 |
---|---|
| 将會傳入這條語句的參數的類全限定名或别名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器(TypeHandler)推斷出具體傳入語句的參數,預設值為未設定(unset)。 |
| 用于引用外部 parameterMap 的屬性,目前已被廢棄。請使用行内參數映射和 parameterType 屬性。 |
| 将其設定為 true 後,隻要語句被調用,都會導緻本地緩存和二級緩存被清空,預設值:(對 insert、update 和 delete 語句)true。 |
| 這個設定是在抛出異常之前,驅動程式等待資料庫傳回請求結果的秒數。預設值為未設定(unset)(依賴資料庫驅動)。 |
| 可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。 |
| (僅适用于 insert 和 update)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由資料庫内部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關系型資料庫管理系統的自動遞增字段),預設值:false。 |
| (僅适用于 insert 和 update)指定能夠唯一識别對象的屬性,MyBatis 會使用 getGeneratedKeys 的傳回值或 insert 語句的 selectKey 子元素設定它的值,預設值:未設定( )。如果生成列不止一個,可以用逗号分隔多個屬性名稱。 |
| (僅适用于 insert 和 update)設定生成鍵值在表中的列名,在某些資料庫(像 PostgreSQL)中,當主鍵列不是表中的第一列的時候,是必須設定的。如果生成列不止一個,可以用逗号分隔多個屬性名稱。 |
| 如果配置了資料庫廠商辨別(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);
}