ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

第二章-Mybatis源码解析-以xml方式走流程

2026/9/23 22:34:59 拓冰建站 浏览量
第二章-Mybatis源码解析-以xml方式走流程

第二章-Mybatis源码解析-以xml方式走流程

2.1 准备阶段

xml执行大概经过以下几个步骤:

1)xml配置资源位置

2)根据配置资源构建Environment(环境)、Configuration(配置信息)、TransactionFactory(事务工厂类)、typeAliases(类型别名)、typeHandlers(类型处理器)

3)生成SqlSessionFactory

4)创建SqlSession

5)包装语句(包括xml语句的解析)

6)处理类型

7)处理参数

8)执行语句(涉及缓存)

9)处理结果

10)包装结果并返回,结束流程

实际流程比上面的还是要复杂很多,但是Mybatis帮我们都封装的很好,实际使用时,就几行代码,如下:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Blog blog = sqlSession.selectOne("polyphagic.code.mybatis.BlogMapper.selectBlog1", 1);

那我们结合流程和以上的几行代码来看看Mybatis到底做了什么。

String resource = “mybatis-config.xml”;
InputStream inputStream = Resources.getResourceAsStream(resource);

这两行代码的作用是根据地址信息,查找xml配置资源,并转化成输入流

引用官方文档的一句话: 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

好,那我们看看 new SqlSessionFactoryBuilder().build(inputStream) 这个方法做了什么

public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);
}public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// 最终看这一行,先做xml解析,然后再build构建,继续往里面深挖return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}

XML配置信息解析

xml的解析过程读者可以去看看有关xml解析的文章,我这里就不讲xml如何解析xml文件和结构。解析完后,就能得到完整的Configuration对象,里面包括typeAliases、typeHandler、映射器mapper、全局属性、日志实现、插件、环境等内容,后续流程中都会用到这些。

public Configuration parse() {if (parsed) { // 重复解析判断throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true; // 设置解析标志// 从xml的configuration的节点开始解析parseConfiguration(parser.evalNode("/configuration"));return configuration;
}private void parseConfiguration(XNode root) {try {//issue #117 read properties first// 首先解析propertiespropertiesElement(root.evalNode("properties"));// 解析settings Properties settings = settingsAsProperties(root.evalNode("settings"));/*通过settings中的配置,拿到vfs的实现,并设置到configuration中这个vfs网上很多文章都说是“虚拟文件系统”,其实这压根就不是什么文件系统,这个我们可以看VFS的类实现逻辑和提供的方法入口来看,实际上它只是用于更方便的访问应用程序服务器中的资源,比如查找类文件实现定义时,需要列出某个目录的地址列出所有文件来查找;判断某个文件是不是jar文件等等;其实把它理解成一个工具类就行,扯上“虚拟文件系统”这个概念就有点大了*/loadCustomVfs(settings);/*设置日志实现方式:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING*/loadCustomLogImpl(settings);// 类型别名解析,解析后的类型别名都存放在类TypeAliasRegistry的typeAliases(HashMap)中,读者有兴趣的可以去看下这个类的,里面包括了8个基础类型及其数组和包装类和数组、日期类型、数字类型、Object类型、List、Map、HashMap、ArrayList等常用类型的另外定义,这就是为什么我们在xml写sql语句时,类型Map可以直接写,而不需要写成全限定名的原因。typeAliasesElement(root.evalNode("typeAliases"));// 解析自定义的插件设置,具体细节等实际做插件处理时再讲pluginElement(root.evalNode("plugins"));// 对象工厂解析:每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现,这块自定义实现,官方文档中有描述。objectFactoryElement(root.evalNode("objectFactory"));// 解析对象包装类工厂配置objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 解析反射器工厂配置reflectorFactoryElement(root.evalNode("reflectorFactory"));// 将settings中设置的内容都设置到 configuration 中,有兴趣的可以回头看官网对settings可设置的内容详细了解settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631// 像出现 issue #631 这种注释时,就表示是为了解决编号631的bug,这是业界规范描述// 解析环境设置,MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。environmentsElement(root.evalNode("environments"));// 解析数据库厂商标识配置databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 解析类型处理器配置:类型处理器也都是通过类TypeHandlerRegistry中的HashMap来维护的,里面有定义Mybatis帮我们预先设置的许多默认数据类型的类型处理器,读者可以打开TypeHandlerRegistry类源码看一下就知道,这块内容也可以自定义typeHandlerElement(root.evalNode("typeHandlers"));// 解析映射器设置,映射器说白了,就是写sql的地方,Mybatis支持xml和注解形式,所以,这里可以配置两种形式,一是xml文件目录;二是Java接口类文件目录,Mybatis官网也有描述,可以自己去看,这一步还实现了对sql语句的进一步解析,这块可以看`章节2.2`mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}

构建SqlSessionFactory

上面解析配置信息后,就要构建SqlSessionFactory了,通过类SqlSessionFactoryBuilder来构建,看如下代码

  public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config); // 实际上就是创建DefaultSqlSessionFactory实例}

2.2 映射器配置解析

代码实现在XMLConfigBuilder.mapperElement方法中

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");// 使用完全限定资源定位符(URL)String url = child.getStringAttribute("url");// 使用映射器接口实现类的完全限定类名String mapperClass = child.getStringAttribute("class");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());// mapper文件内容解析,主要就是sql、参数map、结果map、缓存配置的解析,看`图2-1`,后续章节会对这块内容做细致的解析工作mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());// mapper文件内容解析,主要就是sql、参数map、结果map、缓存配置的解析,看`图2-1`,后续章节会对这块内容做细致的解析工作mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {// 加载mapper接口类Class<?> mapperInterface = Resources.classForName(mapperClass);// 添加到configuration的mapperRegistry注册器的knownMappers(HashMap)中,里面也涉及对mapper接口类的解析,主要应用在注解方式,所以留待后续讲解注解方式时再讲configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}

​ 图2-1
在这里插入图片描述

后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。
在这里插入图片描述