Mybatis作为开发中最常用的数据持久层框架,深受广大程序员喜爱。下面我将通过debug分析mybatis加载资源和执行sql的流程:
一、配置mybatis-config.xml:以下配置为求简略,直接从官网搬运MyBatis官网
加载mybatis配置文件,并创建sqlsessionFactory:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
debug断点进入build方法:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {//获取一个xml解析器XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);//解析xmlreturn build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {if (inputStream != null) {inputStream.close();}} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}
}
进入parse.parse():可以看到是由Configuration 的parse方法加载并解析Mybatis-config.xml配置文件:
public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;//解析node、configurationparseConfiguration(parser.evalNode("/configuration"));return configuration;
}
通过断点获取结果:parser.evalNode("/configuration")
xnode:由此可见和我们再配置文件中的配置是一致的:
配置文件截图:
进一步进入:parseConfiguration:此段代码揭示了,mybatis解析配置文件的顺序:
private void parseConfiguration(XNode root) {try {// issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}
扩展:parseConfiguration 解释了mybatis解析配置文件的顺序,由此可见,我们再配置文件中的属性配置不能随便变更顺序。
第一段:解析properties文件:
propertiesElement(root.evalNode("properties"));

将解析结果保存到:XPathParser
parser.setVariables(defaults); configuration.setVariables(defaults);
public class XPathParser {private final Document document;private boolean validation;private EntityResolver entityResolver;private Properties variables;private XPath xpath;
}

解析config中占位符,替换:

解析mapper:
mapperElement(root.evalNode("mappers"))

解析mapper,由源码可见,mapper从,package->resource->url->class这个顺序解析
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);try (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);try (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.parse()
public void parse() {//判断是否已加载过mapperif (!configuration.isResourceLoaded(resource)) {//解析mapperconfigurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();
}
parser.evalNode("/mapper"):解析mapper

进入配置element
private void configurationElement(XNode context) {try {//获取namespaceString namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {throw new BuilderException("Mapper's namespace cannot be empty");}//获取各种mapper策略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"));//解析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加入已加载的rsource中,将将返回值类型方法mapper中,均操作的是:configuration
private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class> boundType = null;try {boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {// ignore, bound type is not required}if (boundType != null && !configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}
}
加入configuration.addMappedStatement(statement)
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, boolean dirtySelect) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement;
}
扩展:
不可随意更改configuration配置属性位置:


扩展二:mybatis mapper的几种方式:resource url class pacakge
