这篇文章我们来看看最重要的mappers标签是怎样解析的,Mybatis在mappers标签中引入所有的***Mapper.xml
<mappers>
<mapper resource="sqlMap/messageMapper.xml" />
</mappers>
Mybatis支持通过包和具体的xml文件来引入Mapper,如果是向上面那样通过xml文件来引入Mapper的话,Mybatis还可以分别支持resource、url、classs三种方式,我们一般都是通过resource="..."的形式来引入具体的Mapper。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
Mapper的解析是从XMLMapperBuilder的configurationElement方法开始的,这里会分别解析Mapper的所有标签,包括cache-ref,cache,/mapper/parameterMap,/mapper/resultMap,/mapper/sql,select | insert | update | delete标签,其中/mapper/resultMap,/mapper/sql,select | insert | update | delete标签是必须的
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"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
首先每个Mapper中都必须配置namespace属性,namespace是MyBatis面向接口编程的时候必须的标签,根据namespace就可以将接口文件和Mapper文件建立一对一的对应关系,这也就是为什么我们不需要有具体的实现类就可以进行数据库操作的原因,至于具体是怎么实现的,后面会有专门的文章进行介绍
这里只介绍最有代表意义的resultMap标签和select | insert | update | delete标签
resultMap标签
result配置如下
<resultMap type="net.klq.bean.Message" id="message">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="command" jdbcType="VARCHAR" property="command" />
<result column="description" jdbcType="VARCHAR" property="description" />
<result column="content" jdbcType="VARCHAR" property="content" />
</resultMap>
resultMapElement(...)方法负责将resultMap标签和其子标签中的所有内容解析出来存放到resultMapping中,最后通过ResultMapResolver对象将所有resultMap的信息都保存到configuration对象的resultMap属性中,该属性的类型是Map,将resultMap标签存放到该属性中时以namespace.id的形式存放。值即为该resultMap中包含的所有信息
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
System.err.println("ValueBasedIdentifier:"+resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
System.err.println("id:"+id);
//属性的值为null则返回null,不为null则返回属性对应的值
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//根据type的全路径名创建一个class对象
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//每一个result标签的信息会存储到一个resultMapping对象中,resultMap标签下所有的result和id标签都方法哦resultMappings中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();//resolve方法会将resultMap放到configuration中
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
select | insert | update | delete标签
该标签是mybatis所有标签中最复杂的
在mybatis中由XMLStatementBuilder类负责该标签的解析工作,解析该标签的方法如下,这里还是将select | insert | update | delete标签的所有信息解析出来存放到configuration对象的MappedStatement属性中,select | insert | update | delete标签的属性很多,在以后具体使用时再讲解每个属性的作用,在这些标签中最重要的就是动态sql的解析了,在代码1处开始动态标签的解析也就是具体sql的解析,下面来看看动态标签是如何被解析的
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);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
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;
//将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//将其设置为 true,将会导致本条语句的结果被二级缓存
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
//在解析之前整理碎片?没理解,没有这两行代码一样可以运行
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// 1
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))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
动态标签的解析工作是由XMLScriptBuilder类来负责的,解析sql动态标签很好的利用了java的多态的特性,在mybatis中由下面这些类组成了mybatis丰富的动态标签,在一条动态sql中,对应的动态标签的内容会存放到对应的***SqlNode类中
让我们看看源码是如何解析动态标签的,首先拿到一个动态sql,将sql按标签进行分割,通过for循环来判断每一个标签,如果该标签是文本节点,那么判断是否是动态文本节点,如果是动态文本节点则创建一个TextSqlNode对象将该节点信息存储到其中,然后将该对象放到list中,如果不是动态文本节点则创建一个StaticTextSqlNode对象将节点信息存储到其中。如果该标签是动态标签节点,则根据标签名创建对应的标签对象,然后解析动态标签节点
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));
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);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
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;
}
IfHandler的handleNode(...)实现如下,在该方法中递归的调用parseDynamicTags(...)方法,直到将所有动态标签的文本内容解析出来为止,这就是该递归的结束条件,所有标签都解析完成后将这些标签的信息存储到MixedSqlNode中,MixedSqlNode是MappedStatement的一个属性,这样
select | insert | update | delete标签的所有信息就都存储到了configuration对象的MappedStatement属性中了
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}