概述
Statement是我們平時sql的載體,一條sql代表一個Statement,來看下mybatis如何解析Statement。
接着上篇最後的入口
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
public class XMLStatementBuilder extends BaseBuilder {
/**
* mapper構造助手
*/
private final MapperBuilderAssistant builderAssistant;
/**
* statement節點
*/
private final XNode context;
/**
* 要求的數據庫id(忽略)
*/
private final String requiredDatabaseId;
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
super(configuration);
this.builderAssistant = builderAssistant;
this.context = context;
this.requiredDatabaseId = databaseId;
}
這裏元數據是,mapper構造助手、statement節點。
parseStatementNode 解析xml
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
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
// ①include sql片段解析,替換<include />
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
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 />
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,sql載體
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");
// ②使用xml構造助手生成MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
statement標籤上定義的屬性比較多,一些簡單的屬性解析之後,直接保存結果。
對某些標籤會做稍複雜一點的處理。
include解析
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
// 應用include標籤
includeParser.applyIncludes(context.getNode());
public void applyIncludes(Node source) {
// properties讀取
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables();
Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll);
// 應用
applyIncludes(source, variablesContext, false);
}
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
if (source.getNodeName().equals("include")) {
//通過refid 查找出sql節點,include實際是引入sql標籤
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
Properties toIncludeContext = getVariablesContext(source, variablesContext);
// 遞歸處理嵌套<include />
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);
}
toInclude.getParentNode().removeChild(toInclude);
} else if (source.getNodeType() == Node.ELEMENT_NODE) {
if (included && !variablesContext.isEmpty()) {
// replace variables in attribute values
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()) {
// 佔位符替換
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
}
include標籤解析實際是查找當前namespace下的sql片段,並在真正解析Statement之前,將其替換成Sql標籤。
然後會將整個sql,可能是動態的可能是靜態的轉爲SqlSource。
MappedStatement 構造
MappedStatement主要是包含了對Statement標籤解析的結果,完完整整的描述個Statement。
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}
構造過程:
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 屬性挨個設置到builder中
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
// ①resultMap解析
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
// ②轉換ParameterMap
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 構造實例
MappedStatement statement = statementBuilder.build();
//保存到configuration中
configuration.addMappedStatement(statement);
return statement;
}
其實構造過程也比較單純,就是將xml的上定義的標籤解析並保存下來。
resultMap解析
①處,Statement上ResultMap解析展開一下。
private List<ResultMap> getStatementResultMaps(
String resultMap,
Class<?> resultType,
String statementId) {
// 在當前命名空間下查找
resultMap = applyCurrentNamespace(resultMap, true);
/*
* 存儲ResultMap結果
*/
List<ResultMap> resultMaps = new ArrayList<>();
if (resultMap != null) {
/*
* 支持多個 ,分隔,存儲過程纔會涉及多個ResultMap
* 普通sql考慮一個的情況即可
*/
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
try {
/*
* 根據resultMap的id從當初Configuration中獲取ResultMap實例
* 在解析xml的時候Configuration就已經將所有ResultMap存下來了
*/
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
}
}
}
/*
* 如果使用的ResultType,那麼會自動生成一個ResultMap
*
*/
else if (resultType != null) {
ResultMap inlineResultMap = new ResultMap.Builder(
configuration,
statementId + "-Inline",
resultType,
new ArrayList<>(),
null).build();
resultMaps.add(inlineResultMap);
}
return resultMaps;
}
這裏看到當配置了ResultType時也會爲其創建一個ResultMap。
ParameterMap解析(由於官方正在漸漸廢棄,所以略過)
總結
解析的過程比較簡單,其中包含了一些實體的轉換,最終解析後構造MappedStatement存儲。