上篇文章说过,mybatis在执行sql语句的时候,通过id获取configuration中mappedStatements的 MappedStatement对象,每个MappedStatement对象对应着增删改查语句,也就是我们配置文件中增删改查标签配置或是注解@select @delete…等等配置,这篇文章我们来看一下是如何解析的以及标签相关属性, mybatis中sql的解析分为两种情况:xml文件解析,注解的解析。我们来看下xml的解析。
从源码入手
XMLMapperBuilder为mapper映射文件解析的类
//XMLMapperBuilder.parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//mapper节点解析
configurationElement(parser.evalNode("/mapper"));
//标识解析完成
configuration.addLoadedResource(resource);
//解析完成绑定命名空间,逻辑上篇已经说过
bindMapperForNamespace();
}
//处理解析未完成节点(解析节点时报错不会抛出异常,会记录到未完成解析列表)
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置当前命名空间,不再赘述
builderAssistant.setCurrentNamespace(namespace);
//下面两个节点是二级缓存相关,后续博客再述
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析parameterMap。mybaits官网标记已废弃不再关注
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//resultMap解析
resultMapElements(context.evalNodes("/mapper/resultMap"));
//sql片段解析
sqlElement(context.evalNodes("/mapper/sql"));
//增删改查节点解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
从上面代码我们可以了解到映射文件mapper节点下的子节点只有如上代码中出现的几个。
resultMap节点
官网介绍:resultMap节点是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
这个元素通过定义外部的resultMap来解决复杂的映射关系以及数据列与属性列不匹配的问题,很好很强大
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//所要映射的类型
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//用于继承其他的resultMap
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//获取javaBean的Class类对象,首先通过别名获取,别名后续再述
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
//构造resultMapping集合 一个mapping就是就是一个映射关系(可能是一列,也可能关联了一个ResultMap或者关联了一个查询语句返回值)
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
//通过构造方法创建mapping
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//这玩意官网解释是根据列的值得不同将结果映射成不同类型,反正我也没用过
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
//对于元素节点的解析,包括id,result,collection,association等创建mapping
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//最终构造ResultMap加入到configuration中的resultMaps对象中,key为id+namespace
//在sql执行的时候就是去configuration中查找ResultMap进行相应处理
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
关于ResultMap元素节点的使用,mubatis官网很详细,不再粘贴复制了,通过collection,association等元素可以创建复杂的映射关系。
sql片段解析
这个的解析就比较简单,直接看代码
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
//mybaits支持一个项目中支持多个数据库,databaseId这个标识了使用的数据库
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
//将sql片段添加到configuration中的唯一标识,一般为id+namespace,id不能有.其他元素的id也是一样
id = builderAssistant.applyCurrentNamespace(id, false);
//requiredDatabaseId这个为当前数据源使用的数据库标识,只会加载带又databaseId
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//然后加入到sqlFragments,其实这个就是configuration中的sqlFragments,他们指向相同对象,所以在configuration中不会看到put方法
sqlFragments.put(id, context);
}
}
}
mybatis对于多数据库的支持
1.需要在config.xml配置中加入如下配置,
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="Oracle" value="oracle"/>
<property name="MySQL" value="mysql"/>
<property name="DB2" value="db2"/>
</databaseIdProvider>
DB_VENDOR为mybatis实现的类,这个类主要是用来通过数据源获取databaseId的。
name为根据数据源获取的厂商名字,value是用在配置元素databaseId属性的值。
2.在sql或者增删改查节点使用属性databaseId标识所需数据库,如sqlserver
增删改查节点解析
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
//判断是否匹配的数据库语句上面已经提到过
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
//此属性官网标注已废弃
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
//resultMap 与resultType不能同时存在
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
//默认XMLLanguageDriver
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
//JDBC对返回结果的不同处理,反正我也用过
String resultSetType = context.getStringAttribute("resultSetType");
//要操作sql的对象,默认PREPARED,即PreparedStatement使用预编译处理
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//执行sql之前是否清空缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//是否使用二级缓存,select默认使用二级缓存后续再述
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
//这个我也没见过,自己百度吧感兴趣
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
//解析include片段,移除include元素
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
//对于selectKey元素的处理,同时在移除该标签下的selectKey元素内容,针对insert和update
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//创建对于sql内容处理的类,分为静态sql和动态sql两种情况,具体作用下篇介绍
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//很少用
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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;
}
//在此方法中,最终构建一个MappedStatement对象加入到configuration中的mappedStatements中去,id一般为namespace加该节点的id
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
关于sqlSource
sqlSource是一个接口,在mybatis中有四个实现,实现的功能对于增删改查元素中sql内容进行处理的逻辑。
看下代码:
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
//生成处理动态sql的类
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//生成处理静态sql的类
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
//而sql是否动态的判断
protected MixedSqlNode 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));
//text节点或者CDATA节点
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()) {
contents.add(textSqlNode);
//这里设置了为true,那接着看下上面的isDynamic方法
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { //其他元素节点
//看下noHandlerMap初始化,mybatis中增删改查节点如下(除去上面已经移除的selectKey,include),不在包含isNotEmpty等节点
//private void initNodeHandlerMap() {
//nodeHandlerMap.put("trim", new TrimHandler());
//nodeHandlerMap.put("where", new WhereHandler());
//nodeHandlerMap.put("set", new SetHandler());
// nodeHandlerMap.put("foreach", new ForEachHandler());
//nodeHandlerMap.put("if", new IfHandler());
//nodeHandlerMap.put("choose", new ChooseHandler());
// nodeHandlerMap.put("when", new IfHandler());
//nodeHandlerMap.put("otherwise", new OtherwiseHandler());
//nodeHandlerMap.put("bind", new BindHandler());
//}
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
// private GenericTokenParser createParser(TokenHandler handler) {
//return new GenericTokenParser("${", "}", handler);
// }
//创建GenericTokenParser,解析是否包含${},如果有则为动态sql
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
到此缕了下xml中配置到mappedStatement对象的解析,那么从mappedStatement对象到执行,又是怎么样的呢?