config文件mapper的配置方式有如下几种
因为指定xml的解析方式更加直接,代码更加清晰,所以先以xml配置的方式调试源码,后面再说包解析和class解析。
一个简单的mapper.xml文件,内容如下
其中namespace不能为空,对应此mapper的Dao接口,子标签的id对应相应的方法
mapper文件的解析在初始化 configuration 的时候,XMLConfigBuilder.parseConfiguration() 方法内调用mapperElement() 方法完成。如下
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// package
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) {
//resource
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) {
// url
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.");
}
}
}
}
}
XMLMapperBuilder用来解析mapper,parse 方法
// XMLMapperBuilder#parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
资源未加载,解析 mapper 节点
configurationElement(parser.evalNode("/mapper"));
private void configurationElement(XNode context) {
try {
// 必填,对应的java接口
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 已废弃
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// resultMap
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 可被其他语句引用的可重用语句块
sqlElement(context.evalNodes("/mapper/sql"));
// 解析Statement
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);
}
}
- cache 解析完毕后放在 configuration 的 caches 集合中
-
resultMap
元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBCResultSets
数据提取代码中解放出来, 并在一些情形下允许你做一些 JDBC 不支持的事情。 放在 configuration 的 resultMaps 集合中 - sql 解析完毕后放在 configuration 的 sqlFragments 集合中
下面重点解析Statement节点,解析Statement节点,保存到sqlFragments 中,解析方法为XMLStatementBuilder.parseStatementNode(),步骤如下
解析statementNode属性
属性有id、fetchSize、timeout、parameterMap、parameterType、resultMap、statementType (默认PREPARED),默认非select查询flushCache为true、select查询useCache为true-
解析include 子节点
将子节点的include节点替换为sql语句,然后移除include节点includeParser.applyIncludes(context.getNode());
-
解析selectKey
processSelectKeyNodes(id, parameterTypeClass, langDriver);
-
解析sql,创建sqlSource
// langDriver 默认为XMLLanguageDriver langDriver.createSqlSource(configuration, context, parameterTypeClass);
createSqlSource方法调用XMLScriptBuilder.parseScriptNode()方法返回SqlSource对象,
public SqlSource parseScriptNode() { // 解析动态节点 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
parseDynamicTags方法如下
protected MixedSqlNode parseDynamicTags(XNode node) { List
contents = new ArrayList (); 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 = 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); } 如果statement节点下不存在子节点,但是文本中包含${},那么认为是动态节点;如果statement节点下存在子节点,如trim,if,where,那么statement也动态节点。 最后返回
new MixedSqlNode(contents)
。最后,如果包含动态节点返回DynamicSqlSource对象,否则返回RawSqlSource对象,SqlSource构造函数中会进行占位符替换成
?
。 创建KeyGenerator
创建MappedStatement 并添加到configuration.mappedStatements中
configuration.addLoadedResource(resource);
添加已解析资源路径
bindMapperForNamespace();
根据namespace寻找相应的mapper类,添加已加载资源和mapper类
boundType = Resources.classForName(namespace);
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
到此一个Mapper解析已经完毕,以上是以xml配置的方式解析mapper,在使用package方式配置mapper的时候,需要对mapper文件的名称及位置需要进行特别处理,就是包扫描的是根据dao接口所在文件夹配置的,默认会根据该文件下的接口,去寻找资源路径下 mapper.xml 文件,寻找的路径和文件名是和接口一致的。举个例子:
在 maven 项目中,如果我配置的是
该路径下有个BlogDao.java
接口,那我需要在resource文件夹下有com/zero/mybatis/dao/BlogDao.xml
,正确配置上。如图
下面源码解释,还是XMLConfigBuilder.parseConfiguration() 方法
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();
}
方法调用会进入到MapperRegistry.addMapper方法,这个方法是根据配置包下的class文件,进行mapper注册,配置class也是使用此方法进行注册
// MapperRegistry.addMapper
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 代理工厂,用来生成dao的代理
knownMappers.put(type, new MapperProxyFactory(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 这里解析
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
MapperAnnotationBuilder.loadXmlResource()方法根据class寻找mapper文件
public void parse() {
//...
loadXmlResource();
//...
}
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 这里就是对应加载的xml mapper文件,可以看到是类全名替换成路径 + .xml
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
xmlResource 可以看到是类全名替换成路径 + .xml,解析方式和xml配置的解析方式一致。到此Mapper解析完毕。