Mybatis 源码分析(一)配置文件加载流程
1、项目构建
引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
创建mybatis配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<!--配置DAO层位置(mapper.xml对应接口路径)-->
<package name="com.xiaofeizhu.dao"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/xiaofeizhu"/>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- mapper.xml文件路径-->
<mapper resource="mapper/UserInfoDao.xml" />
</mappers>
</configuration>
2、配置文件对象创建
2.1 概念说明
- Java是面向对象的编程语言,即万物皆可对象的理念,所以配置文件在Java必然会被视为对象;
- ORM:对象-表-关系的映射 【mybatis】
- OXM:对象-XML文件-关系 【XPath:XPath 是一门在 XML 文档中查找信息的语言】
2.2 Mybatis如何处理配置文件
结论:Mybatis会将配置文件映射成Configuration对象
方式: Mybatis会通过XPathParser解释器去解析配置文件
2.3 Mybatis创建Configuration对象代码跟踪
package com.xiaofeizhu;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class Test {
/**
* Mybatis读取配置文件创建SQLSession
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//【代码分析】 这里的build()方法回去加载配置文件的输入流,所以可以断定配置文件对象的创建发生在这个方法,进一步跟踪build()方法
SqlSession sqlSession = sqlSessionFactory.openSession();
}
}
//跟踪进入的build()方法
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
//实际指向的重载的build()方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
//【代码分析】 可以看到inputStream输入流实际用来创建了XMLConfigBuilder对象parser,之后调用了parser.parse()方法,所以进一步跟踪parse()方法
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
//【代码分析】 这里可以看到这个parse()方法返回的是Configuration对象,所以可以判断mybatis-config.xml文件被映射成Configuration对象是发生在这一步,需要进一步跟踪this.parseConfiguration(this.parser.evalNode("/configuration"))里发生了什么
//【代码说明】 this.parseConfiguration(this.parser.evalNode("/configuration"));这里的this.parser.evalNode("/configuration")是XPath的语法,意思是加载configuration节点下的所有内容{configuration就是mybatis-config.xml的根标签}
}
}
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
//【代码分析1】 上一步跟踪的代码:this.parseConfiguration(this.parser.evalNode("/configuration"));而parseConfiguration(XNode root)里的参数是Xnode对象{mybatis通过XPathParser解释器将myabtis-config.xml对象解析成XNode对象,及各个标签的映射,限于篇幅不做赘述,有兴趣可以跟一下代码或者看一下XPath语法}
//【代码分析2】parseConfiguration()方法里读取XNode对象各个节点的值赋值给Configuration对象里的各个参数
//【重点说明1】 这里出现的各个属性的顺序就是mybatis-config.xml配置文件的顺序,也是为啥配置的时候标签属性有固定的顺序,因为XPathparser是按照定义好的标签顺序去解析的;
//【重点说明2】this.mapperElement(root.evalNode("mappers")); 这个属性的赋值需要注意,这里mapperElement里会进行读取mapper文件的路径解析赋值这样一个过程,需要跟踪一下代码分析
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
//【代码分析】 这里有三种情况对应<mapper/>里的三种类型的属性,譬如这里就是指 <mapper resource="mapper/UserInfoDao.xml" /> 通过resource属性来制定mapper.xml文件路径
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
//【代码分析】这里可以看到获取mapper.xml文件路径,创建输入流创建对象的过程,类似之前读取mybatis-config.xml创建configuration对象的过程,这里可以跟踪一下mapperParser.parse()方法
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
//【代码分析】 类似之前读取mapper.xml文件里<mapper>标签下的所有属性内容,跟踪一下configurationElement()方法
this.configuration.addLoadedResource(this.resource);configurationElement()方法
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
this.sqlElement(context.evalNodes("/mapper/sql"));
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
//【代码分析】 这里可以看到这个方法里是在读取mapper.xml文件里各个标签的属性,因为核心处理过程是类似的,这里的最终的目的是将所有的mapper.xml文件封装成MappedStatement对象赋值到Configuration对象的属性(mappedStatements)里,额,具体跟踪就不放在这一步一步跟踪了,后续跟踪的核心的对象,会在Mybatis核心对象里给出【主要太累人,哈哈,偷懒偷懒】
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
}
}
//Configuration对象属性【对应着myabtis-config.xml各个标签的值】
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel;
protected boolean cacheEnabled;
protected boolean callSettersOnNulls;
protected boolean useActualParamName;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope;
protected JdbcType jdbcTypeForNull;
protected Set<String> lazyLoadTriggerMethods;
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType;
protected AutoMappingBehavior autoMappingBehavior;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
protected Properties variables;
protected ReflectorFactory reflectorFactory;
protected ObjectFactory objectFactory;
protected ObjectWrapperFactory objectWrapperFactory;
protected boolean lazyLoadingEnabled;
protected ProxyFactory proxyFactory;
protected String databaseId;
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry;
protected final InterceptorChain interceptorChain;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final TypeAliasRegistry typeAliasRegistry;
protected final LanguageDriverRegistry languageRegistry;
protected final Map<String, MappedStatement> mappedStatements;
protected final Map<String, Cache> caches;
protected final Map<String, ResultMap> resultMaps;
protected final Map<String, ParameterMap> parameterMaps;
protected final Map<String, KeyGenerator> keyGenerators;
protected final Set<String> loadedResources;
protected final Map<String, XNode> sqlFragments;
protected final Collection<XMLStatementBuilder> incompleteStatements;
protected final Collection<CacheRefResolver> incompleteCacheRefs;
protected final Collection<ResultMapResolver> incompleteResultMaps;
protected final Collection<MethodResolver> incompleteMethods;
protected final Map<String, String> cacheRefMap;
}
//MappedStatement对象属性【对应着mapper.xml文件各个标签属性】
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}