MyBatis解析XML的SQL語句節點或註解@Select等,最終會封裝成MappedStatement對象,存進configuration裏,本篇分析XML解析方式
從XMLMapperBuilder類的buildStatementFromContext方法開始
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) {
//構建所有語句,一個mapper下可以有很多select
//語句比較複雜,核心都在這裏面,所以調用XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//核心XMLStatementBuilder.parseStatementNode
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//如果出現SQL語句不完整,把它記下來,塞到configuration去
configuration.addIncompleteStatement(statementParser);
}
}
}
上面如果有指定數據庫標誌,那會調用兩次buildStatementFromContext方法,而該方法的核心在於statementParser的parseStatementNode方法。
之所以在buildStatementFromContext方法,是因爲在parseStatementNode方法中,會調databaseIdMatchesCurrent進行判斷過濾是否進行MappedStatement構建。在指定數據庫標識的情況下,如果SQL語句節點的databaseId屬性也契合的話,優先級最高。
/**
* @param id SQL語句節點的id屬性
* @param databaseId 節點中databaseId屬性
* @param requiredDatabaseId configuration中的變量;數據庫標誌
* @return
*/
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;//不一致返回false
}
} else {
if (databaseId != null) {
//存在databaseId屬性直接返回false,因爲上面的if已經做了判斷
return false;
}
// skip this statement if there is a previous one with a not null databaseId
//保證是帶命名空間前綴
id = builderAssistant.applyCurrentNamespace(id, false);
//檢查是否存在相同id的MappedStatement(不包括沒完整構建出來的)
if (this.configuration.hasStatement(id, false)) {
//存在,那就拿到之前的
MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
//如果之前的存在databaseId屬性,那就返回false
//因爲之前的肯定通過了上面和requiredDatabaseId比較的判斷
if (previous.getDatabaseId() != null) {
return false;
}
}
}
return true;
}
下面開始看核心解析方法:
//解析語句(select|insert|update|delete)
//<select
// id="selectPerson"
// parameterType="int"
// parameterMap="deprecated"
// resultType="hashmap"
// resultMap="personResultMap"
// flushCache="false"
// useCache="true"
// timeout="10000"
// fetchSize="256"
// statementType="PREPARED"
// resultSetType="FORWARD_ONLY">
// SELECT * FROM PERSON WHERE ID = #{id}
//</select>
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
//如果databaseId不匹配,退出,上面已列出
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//暗示驅動程序每次批量返回的結果行數
Integer fetchSize = context.getIntAttribute("fetchSize");
//超時時間
Integer timeout = context.getIntAttribute("timeout");
//引用外部 parameterMap,已廢棄
String parameterMap = context.getStringAttribute("parameterMap");
//參數類型
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
//引用外部的 resultMap(高級功能)
String resultMap = context.getStringAttribute("resultMap");
//結果類型
String resultType = context.getStringAttribute("resultType");
//腳本語言,mybatis3.2的新功能
String lang = context.getStringAttribute("lang");
//得到語言驅動,默認是XML語言驅動
LanguageDriver langDriver = getLanguageDriver(lang);
//拿到返回類型的Class
Class<?> resultTypeClass = resolveClass(resultType);
//結果集類型,是對jdbc的resultset不同處理,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一種
String resultSetType = context.getStringAttribute("resultSetType");
//語句類型, STATEMENT|PREPARED|CALLABLE 的一種,默認是預編譯型
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
//mybatis把FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE封裝成了枚舉類型
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//獲取命令類型(select|insert|update|delete)
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//如果是查詢默認不清緩存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//是否要緩存select結果
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
//僅針對嵌套結果 select 語句適用:如果爲 true,就是假設包含了嵌套結果集或是分組了,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。
//這就使得在獲取嵌套的結果集的時候不至於導致內存不夠用。默認值:false。
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
//1.解析之前先解析<include>SQL片段,拼裝到當前的sql中,如:
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
//2.解析之前先解析<selectKey>
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 上面解析完後,會把<include>、<selectKey>節點刪掉
//3.解析成SqlSource,一般是DynamicSqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
//(僅對 insert 有用) 標記一個屬性, MyBatis 會通過 getGeneratedKeys 或者通過 insert 語句的 selectKey 子元素設置它的值
String keyProperty = context.getStringAttribute("keyProperty");
//(僅對 insert 有用) 標記一個屬性, MyBatis 會通過 getGeneratedKeys 或者通過 insert 語句的 selectKey 子元素設置它的值
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
//4.主鍵生成器賦值
if (configuration.hasKeyGenerator(keyStatementId)) {
//如果已存在改keyStatementId的主鍵生成器,則直接複製
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
//如果屬性值沒有useGeneratedKeys,就根據isUseGeneratedKeys配置和是否是插入語句決定
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
//5.去調助手類,這裏構建一個MappedStatement存入configuration
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
上面代碼有點長,前面部分是屬性獲取,然後解析<include>、<selectKey>節點並刪除,接着解析封裝一個SqlSource對象,再接着就是主鍵生成器賦值,最後構建MappedStatement。
1.解析之前先解析<include>節點
略  ̄□ ̄||
2. <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) {
String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
String databaseId = nodeToHandle.getStringAttribute("databaseId");
if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
}
}
}
上面調了兩次parseSelectKeyNodes,套路跟文章開頭那裏的類似,真正解析在parseSelectKeyNode方法中
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
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");
//默認是後置設置id
boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
//defaults
boolean useCache = false;
boolean resultOrdered = false;
KeyGenerator keyGenerator = new NoKeyGenerator();
Integer fetchSize = null;
Integer timeout = null;
boolean flushCache = false;
String parameterMap = null;
String resultMap = null;
ResultSetType resultSetTypeEnum = null;
//2.1 解析節點封裝爲sqlSource對象
SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
//設SQL類型爲SELECT
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
//2.2構建一個MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
id = builderAssistant.applyCurrentNamespace(id, false);
MappedStatement keyStatement = configuration.getMappedStatement(id, false);
//2.3添加一個SelectKeyGenerator
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}
解析流程是:解析節點封裝爲sqlSource對象,然後構建一個MappedStatement,接着在爲該添加一個SelectKeyGenerator
2.1解析節點封裝爲sqlSource對象
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
//用XML腳本構建器解析
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
//解析XML的SQL語言封裝成SqlNode,放進集合
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {//動態SQL封裝爲DynamicSqlSource
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {//靜態SQL封裝爲RawSqlSource
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
上面解析SQL語句時,只要sql語句有${}、或者帶有、等標籤,isDynamic標誌會設爲true。
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));
//如果是XML節點文本內容
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()) {//這裏主要判斷sql語句中是否有${}
contents.add(textSqlNode);
isDynamic = true;
} else {
//不是動態SQL,封裝成StaticTextSqlNode
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//這裏就是動態語言節點解析,如<if>、<where>等
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;
}
public interface SqlNode {
boolean apply(DynamicContext context);
}
SqlNode是個接口,實現類有十個,只有一個方法,該方法是對SQL語句作進一步的處理,其中StaticTextSqlNode表示不是動態SQL類型,
2.2構建一個MappedStatement
//增加映射語句
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加上namespace前綴
id = applyCurrentNamespace(id, false);
//是否是select語句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//又是建造者模式
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
statementBuilder.lang(lang);
statementBuilder.resultOrdered(resultOrdered);
statementBuilder.resulSets(resultSets);
setStatementTimeout(timeout, statementBuilder);
//參數映射
setStatementParameterMap(parameterMap, parameterType, statementBuilder);
//結果映射
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
MappedStatement statement = statementBuilder.build();
//建造好調用configuration.addMappedStatement
configuration.addMappedStatement(statement);
return statement;
}
MappedStatement記錄着SQL節點的詳細信息,最終存在configuration中
2.3添加一個SelectKeyGenerator
平時使用selectKey例子如下
<selectKey resultType="java.lang.Long" keyProperty="id" order="BEFORE" >
SELECT LAST_INSERT_ID()
</selectKey>
public interface KeyGenerator {
//定了2個回調方法,processBefore,processAfter
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
KeyGenerator會跟在SQL執行前後分別調用processBefore、processAfter,其中parameter是我們函數的入參
看看SelectKeyGenerator如何實現兩個方法
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (!executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
最終都是調用processGeneratedKeys
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
try {
if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
String[] keyProperties = keyStatement.getKeyProperties();
final Configuration configuration = ms.getConfiguration();
//
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (keyProperties != null) {
//這裏創建一個SimpleExecutor
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
//執行語句
List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
if (values.size() == 0) {
throw ...
} else if (values.size() > 1) {
throw ...
} else {//結果數量爲1才正確
MetaObject metaResult = configuration.newMetaObject(values.get(0));
if (keyProperties.length == 1) {
if (metaResult.hasGetter(keyProperties[0])) {
//返回結果是個對象,判斷是否有getter方法
setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
} else {
//返回結果是個基本類型, 設置id屬性值
setValue(metaParam, keyProperties[0], values.get(0));
}
} else {
//多個屬性處理
handleMultipleProperties(keyProperties, metaParam, metaResult);
}
}
}
}
} catch ...
}
3.解析成SqlSource
參考2.1
4.主鍵生成器賦值
略  ̄□ ̄||
5.去調助手類,這裏構建一個MappedStatement
參考2.2