概述
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 {
private final MapperBuilderAssistant builderAssistant
;
private final XNode context
;
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);
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
);
processSelectKeyNodes(id
, parameterTypeClass
, langDriver
);
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
);
}
statement标签上定义的属性比较多,一些简单的属性解析之后,直接保存结果。
对某些标签会做稍复杂一点的处理。
include解析
XMLIncludeTransformer includeParser
= new XMLIncludeTransformer(configuration
, builderAssistant
);
includeParser
.applyIncludes(context
.getNode());
public void applyIncludes(Node source
) {
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")) {
Node toInclude
= findSqlFragment(getStringAttribute(source
, "refid"), variablesContext
);
Properties toIncludeContext
= getVariablesContext(source
, variablesContext
);
applyIncludes(toInclude
, toIncludeContext
, true);
if (toInclude
.getOwnerDocument() != source
.getOwnerDocument()) {
toInclude
= source
.getOwnerDocument().importNode(toInclude
, true);
}
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()) {
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
;
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
)
.resultMaps(getStatementResultMaps(resultMap
, resultType
, id
))
.resultSetType(resultSetType
)
.flushCacheRequired(valueOrDefault(flushCache
, !isSelect
))
.useCache(valueOrDefault(useCache
, isSelect
))
.cache(currentCache
);
ParameterMap statementParameterMap
= getStatementParameterMap(parameterMap
, parameterType
, id
);
if (statementParameterMap
!= null
) {
statementBuilder
.parameterMap(statementParameterMap
);
}
MappedStatement statement
= statementBuilder
.build();
configuration
.addMappedStatement(statement
);
return statement
;
}
其实构造过程也比较单纯,就是将xml的上定义的标签解析并保存下来。
resultMap解析
①处,Statement上ResultMap解析展开一下。
private List
<ResultMap> getStatementResultMaps(
String resultMap
,
Class
<?> resultType
,
String statementId
) {
resultMap
= applyCurrentNamespace(resultMap
, true);
List
<ResultMap> resultMaps
= new ArrayList<>();
if (resultMap
!= null
) {
String
[] resultMapNames
= resultMap
.split(",");
for (String resultMapName
: resultMapNames
) {
try {
resultMaps
.add(configuration
.getResultMap(resultMapName
.trim()));
} catch (IllegalArgumentException e
) {
throw new IncompleteElementException("Could not find result map '" + resultMapName
+ "' referenced from '" + statementId
+ "'", e
);
}
}
}
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存储。