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