前言
使用mybatis
的时候基本上都需要写一个mapper.xml
以及对应的Mapper.java
。在mapper.xml
中写sql
的时候还需要对应Mapper.java
中的某个方法,常规操作知道如何弄,但是你知道最后mapper.xml
中的、
以及
都对应Java
中什么对象吗?要了解这个,就需要去了解xml
的装载、解析、以及到Java
对象的映射过程。
包图
下面是包中对象的依赖关系,乍眼一看还是蛮复杂的。先解释各个对象的作用,然后结合源码与下面包图说下具体的流程。
对象
SqlSessionFactoryBean
:连接mybatis
与Spring
的桥梁,主要作用是创建SqlSessionFactory
。
XMLMapperBuilder
: 装载了xml
文件,该文件具体是存储在XPathParser
中。
XPathParser
:保存了xml
文件,用于解析xml
。
XMLStatementBuilder
:装载了、
等节点,具体是保存在XNode
中,主要作用是解析出这些节点的属性,然后通过MapperBuilderAssistant
将其保存在Configuration
对象中。
XNode
:保存了xml
的节点,用于解析节点属性。比如获取 中的id等。
MapperBuilderAssistant
:这是个帮助类,将XMLMapperBuilder
,XMLStatementBuilder
解析的xml
属性转换成对应的Java
对象,然后将其保存在Configuration
对象中。
MappedStatement
:等节点对应的
Java
对象。
Configuration
:这个类真是个集大成者,包含mapper.xml
中的cache
、resultMaps
、parameterMaps
等,基本上就是mapper.xml
对应的Java
对象。
流程一装载xml
装载mapper.xml
涉及SqlSessionFactoryBean
、XMLMapperBuilder
以及XPathParser
。首先看的是SqlSessionFactoryBean
的buildSqlSessionFactory
方法,为了方便大家看源码,我只把主要的流程保留了下来,其他旁枝末节都删掉了。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
....
if (!isEmpty(this.mapperLocations)) {
// resource 就是mapper.xml文件,这里遍历所有的mapper.xml文件
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//根据configuration以及mapper.xml文件的输入流创建XMLMapperBuilder
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
// 主要是解析xml文件
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
接下来就是看XMLMapperBuilder
的构造方法。这个构造方法主要就是根据mapper.xml
文件输入流构造XPathParser
,到这里某个mapper.xml
已经装载到XMLMapperBuilder
。
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private final Map sqlFragments;
...
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map sqlFragments) {
// 根据xml文件输入流构造XPathParser
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
}
流程二解析xml各节点。
这一步涉及到XMLMapperBuilder
、XMLStatementBuilder
、XPathParser
、XNode
。
首先看XMLMapperBuilder
,其主要作用获取xml
的节点信息。获取节点信息的主要目的是构造对应节点的Java
对象。比如节点构造的是
MappedStatement
,
节点构造的是ResultMap
。
//XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//parser 就是XPathParser,这一步获取节点
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
...
}
/**
这个方法主要是解析xml,并提取相应节点的属性,
然后将其构造成Java对象,接着将对象放入configuration中
**/
private void configurationElement(XNode context) {
try {
// 获取 中namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 获取 中cache-ref节点
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 获取 中resultMap节点
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);
}
}
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
// XNode 代表一个| | | 节点信息
for (XNode context : list) {
// 创建XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder
主要是获取|
节点中的信息,然后交给MapperBuilderAssistant
创建MappedStatement
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);
.....
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
流程三构造MappedStatement
MappedStatement
就是对应|
节点的Java
对象,其中MappedStatement
有个字段id
,其值为包名.类名.方法名。通过MapperBuilderAssistant
将创建好的MappedStatement
以id
为key
,以MappedStatement
对象为value
放入Configuration
中的mappedStatements
中,mappedStatements
字段其实是一个map
。如果在一个Mapper.java
中存在同名的方法,那么其中肯定有个方法会失效。比如select(Integer id)
与select(String key)
在同一个com.test.Mapper.java
中,这两个方法对应的MappedStatement
存在相同的id
-com.test.Mapper.select
,当他们都要放入Configuration
中,后面放入的肯定要覆盖前面的MappedStatement
。这就是为什么mybatis
在同一个Mapper.java
中不能够重载方法。
MapperBuilderAssistant。
//addMappedStatement 方法
MappedStatement statement = statementBuilder.build();
// 放入configuration中
configuration.addMappedStatement(statement);
Configuration
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
总结
主要流程就是如下:
扫描xml
--->装载xml
--->获取xml
中各节点信息-->根据节点信息构造Java对象-->将Java对象放入configuration