文章目录

  • ​​1.MyBatis 核心流程​​
  • ​​2.启动准备阶段流程​​
  • ​​3.创建 SQlSessionFactory​​
  • ​​4.创建XMLConfigBuilder​​
  • ​​5.创建 XPathParser​​
  • ​​6.解析并设置 configuration 中的属性​​
  • ​​7.解析Mappers标签​​

1.MyBatis 核心流程

Mybatis的核心流程氛围两个阶段,启动准备阶段和执行SQL阶段。

  1. 加载配置XMl文件。
  2. 读取mybatis的dtd描述文件,并且解析xml标签
  3. 通过读取的XMl配置信息生成对应的全局配置对象,以及生成mapper不同方法的SQL映射。
  4. 创建 SqlSessionFactory 完成,使用 SqlSessionFactory 创建 Session。
  5. 根据方法签名,获得mapper对应的代理对象。
  6. 通过JDK动态代理找到实现类,获得数据库连接,然后执行SQL。
  7. 拿到执行结果,并且根据映射关系处理为Java对象。
  8. 执行结束,关闭资源。

其中重点在于5个点:解析xml、生产代理对象、获得代理对象、执行SQL、处理结果集。

2.启动准备阶段流程

  1. 加载配置XMl文件
  2. 读取mybatis的dtd描述文件,并且解析xml标签
  3. 通过读取的XMl配置信息生成对应的全局配置对象,以及生成需要mapper的SQL映射。
  4. 创建 SqlSessionFactory 完成,使用 SqlSessionFactory 创建 Session。

Mybatis 原理之启动阶段_配置文件

  • congfiguration:是Mybatis初始化过程的核心对象,mybatis中几乎全部的配置信息会保存到Configuration中,全局生效。
  • XMLConfigBuilder:用于创建configuration,解析MyBatis配置文件中 configuration 节点的配置信息并填充到 Configuration 对象中
  • XMLMapperEntityResolver :包含于 XMLConfigBuilder中,用于读取本地DTD文件。
  • XPathParser :XPath解析器,对JDK的类做了封装,包含于 XMLConfigBuilder中。XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。
  • document :包含于XPathParser 中,方便后续结合 XPathParser 读取配置文件内容。

3.创建 SQlSessionFactory

建立Session就需要工厂类,建立SqlSessionFactory就需要SqlSessionFactoryBuilder,但是建立Factory就需要先读取并解析XML,因为可以看到build方法依赖于全局配置 configuration。

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
// 准备阶段:将配置文件加载到内存中并生成document对象,然后创建初始化Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析document对象并生成 SqlSessionFactory
return build(parser.parse());
}

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

4.创建XMLConfigBuilder

创建 ​​SqlSessionFactory​​​ 需要依赖​​XMLConfigBuilder​​​ 来读取并且解析配置文件,将配置文件转化为​​Document对象​​​,初始化​​configuration​​ 对象并且根据配置文件填充属性。

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 调用父类初始化configuration
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
// 将Properties全部设置到configuration里面去
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
// 绑定 XPathParser
this.parser = parser;
}

初始化 Configuration 对象

初始化 Configuration 时,会将要用到的 class 和其别名注册到 typeAliases 这个map中。

private final Map<String, Class<?>> typeAliases = new HashMap<>();
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}

5.创建 XPathParser

​XPathParser​​​ 里面包含了很多重要的对象,包括用于读取本地​​DTD文件​​​的​​XMLMapperEntityResolver​​​ 对象、​​Xpath对象​​​、存储xml文件的​​document对象​​​以及​​properties标签定义​​​的键值对集合​​对象variables​​。

Mybatis 原理之启动阶段_初始化_02

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
// 初始化属性
commonConstructor(validation, variables, entityResolver);
// 读取xml文件,保存为 document
this.document = createDocument(new InputSource(inputStream));
}
public class XPathParser {
private final Document document;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;
}
  • document
  • entityResolver
  • variables
  • xpath

6.解析并设置 configuration 中的属性

MyBatis的配置解析都是基于 ​​<configuration>​​ 标签下

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

// XMLConfigBuilder
public Configuration parse() {
// 根据parsed变量的值判断是否已经完成了对mybatis-config.xml配置文件的解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

配置文件的解析需要 xpath的表达式和 document 对象, 根据标签名称匹配到节点, 取出对于的value, 找到节点返回XNode对象, 然后初始化Configuration

//XPathParser
public XNode evalNode(String expression) {
return evalNode(document, expression);
}

解析 Configuration 中每个节点

一切配置都在这里解析并完成属性的设置, 比如设置properties, 创建PooledDataSourceFactory, 解析插件, 处理mapper映射。

private void parseConfiguration(XNode root) {
// issue #117 read properties first
// 解析properties,并设置variables属性,同时设置在parser和configuration中
propertiesElement(root.evalNode("properties"));
// 解析settings
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 设置vfsImpl字段
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);//设置具体的属性到configuration对象
// read it after objectFactory and objectWrapperFactory issue #631
// 环境 设置transactionManager 、dataSource
environmentsElement(root.evalNode("environments"));
// databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
// 映射器 最重要!
mapperElement(root.evalNode("mappers"));
}

解析 envirmonent 节点

由于我们的XML文件中配置了environment 节点,所以会对其进行解析

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 未指定XMLConfigBuilder.environment字段,则使用default属性
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍历子节点
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 与XmlConfigBuilder.environment字段匹配
if (isSpecifiedEnvironment(id)) {
// 创建TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创建DataSourceFactory和DataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建Environment
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 将Environment对象记录到Configuration.environment字段中
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}

7.解析Mappers标签

<mappers>
<package name="com.daley.dao"/>
</mappers>

如果节点名称等于​​package​

  1. 会自动扫描包路径下的所以mapper,
  2. configuration中的mapperRegistry会add对应的package路径
  3. 将最终mapper对应的MapperProxyFactory 添加到knowMappers中
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 处理mapper子节点
for (XNode child : parent.getChildren()) {
// package子节点
if ("package".equals(child.getName())) {
// 自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
// 扫描指定的包,并向mapperRegistry注册mapper接口
configuration.addMappers(mapperPackage);
} else {

// 直接指定Mapper属性
// 获取mapper节点的resource、url、class属性,三个属性互斥
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 如果mapper节点指定了resource或者url属性,则创建XmlMapperBuilder对象,并通过该对象解析resource或者url属性指定的mapper配置文件
if (resource != null && url == null && mapperClass == null) {
// 使用类路径
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
// 创建XMLMapperBuilder对象,解析映射配置文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
// 使用绝对url路径
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
// 创建XMLMapperBuilder对象,解析映射配置文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
// 如果mapper节点指定了class属性,则向MapperRegistry注册该mapper接口
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.");
}
}
}
}
}

Mybatis 原理之启动阶段_mybatis_03

//MapperRegistry
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
// 查找包下所有父类是 Objects.class 的类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
// MapperRegistry.addMapper()
public <T> void addMapper(Class<T> type) {
……
boolean loadCompleted = false;
// 将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
knownMappers.put(type, new MapperProxyFactory<>(type));

// 对被注解接口的解析逻辑
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
}

在添加完mapper映射关系后,会开始对Mapper接口中相关注解的解析工作

  1. 会读取mapper class对应的mapper xml文件, 并且在读取后对文件中的内容进行解析
  2. 变量mapper class每一个方法, 读取标识的注解进行解析, 如resultMap
public void parse() {
String resource = type.toString();
// 检测是否已经加载过该接口
if (!configuration.isResourceLoaded(resource)) {
// 检测是否加载过对应的映射配置文件,如果未加载,则创建XMLMapperBuilder对象解析对应的mapper映射xml文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析@CacheNamespace注解
parseCache();
// 解析@CacheNamespaceRef注解
parseCacheRef();
//开始解析每个方法
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
// 解析@SelectKey,@ResultMap等注解,并创建MappedStatement对象
parseStatement(method);
} catch (IncompleteElementException e) {
// 如果解析过程出现IncompleteElementException异常,可能是引用了未解析的注解,此处将出现异常的方法添加到incompleteMethod集合中保存
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 遍历incompleteMethods集合中记录的未解析的方法,并重新进行解析
parsePendingMethods();
}

loadXmlResource方法会调用parse方法对象xml的mapper中内容进行解析。

public void parse() {
// 判断是否已经加载过该映射文件
if (!configuration.isResourceLoaded(resource)) {
// 处理mapper节点
configurationElement(parser.evalNode("/mapper"));
// 将resource添加到Configuration.loadedResources集合中保存,他是hashset类型的集合,其中记录了已经加载过的映射文件
configuration.addLoadedResource(resource);
// 绑定映射器到namespace
bindMapperForNamespace();
}

// 处理ConfigurationElement方法中解析失败的resultMap节点
parsePendingResultMaps();
// 处理ConfigurationElement方法中解析失败的cache-ref节点
parsePendingCacheRefs();
// 处理ConfigurationElement方法中解析失败的SQL语句节点
parsePendingStatements();
}

configurationElement方法: 对sql, resultMap, parameterMap等节点进行解析

private void configurationElement(XNode context) {
try {
// 获取mapper节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置MapperBuilderAssistant的currentNamespace字段,记录当前命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析cache-ref节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析cache节点
cacheElement(context.evalNode("cache"));
// 解析parameterMap节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析resultMap节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析select、update、insert、delete等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);
}
}

Configuration 创建完毕, 返回创建好的SqlSessionFactory, 然后建后续需要的Transaction、Executor和DefaultSqlSession

SqlSession sqlSession = sqlSessionFactory.openSession();
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取mybatis-config.xml配置文件中配置的Environment对象,
final Environment environment = configuration.getEnvironment();
// 获取TransactionFactory对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建Transaction对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据配置创建Executor对象
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}