当前位置: 首页 > news >正文

MyBatis 源码学习 | Day 2 | MyBatis 初始化

运行阶段划分

根据上一篇MyBatis 源码学习 | Day 1 | 了解 MyBatis中最后使用 MyBatis 操作数据库的代码,我们可以把程序整体运行的流程划分为两个部分:

  1. MyBatis 初始化
  2. 数据读写阶段
/*** 使用 MyBatis 操作数据库** @author nx-xn2002* @date 2024-08-02*/
public class QueryWithMyBatis {public static void main(String[] args) throws IOException {//第一阶段:MyBatis初始化String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//第二阶段:数据读写阶段SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> users = mapper.listAll();users.forEach(System.out::println);User user = mapper.selectUserById(1L);System.out.println(user);sqlSession.close();}
}

今天我们来探究第一阶段:MyBatis的初始化

MyBatis 初始化阶段

在 MyBatis 的初始化阶段,主要包含配置文件的解析和数据库连接等工作,可以看到,依次是调用了 Resource 类下的 getResourceAsStream 方法来把配置文件解析成 Stream 类对象,然后通过 SqlSessionFactoryBuilder 类下的 bulid 方法获取到 SqlSessionFactory 对象来管理数据库连接。我们深入到这两个方法中去

解析配置文件为输入流

Resource 类下的 getResourceAsStream 方法,试图获取到一个 InputStream 类的对象,核心方法的源代码如下:

/*** Returns a resource on the classpath as a Stream object** @param loader   The classloader used to fetch the resource* @param resource The resource to find* @return The resource* @throws java.io.IOException If the resource cannot be found or read
*/
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);if (in == null) {throw new IOException("Could not find resource " + resource);}return in;
}

可以看到,这里是调用了 classLoaderWrapper 对象的同名方法来获取返回值,这个对象是 Resource 类的静态变量,它是在 Resource 类被加载时初始化的,类的加载过程包括:加载 -> 验证 -> 准备 -> 解析 -> 初始化,而 classLoaderWrapper 对象的赋值,就发生在初始化阶段
继续向下定位,可以看到 classLoaderWrapper 对象的这个方法,实际上是调用了另一个重载方法,方法的参数中 ClassLoader 对象变成了 ClassLoader 数组

/*** Get a resource from the classpath, starting with a specific class loader** @param resource    - the resource to find* @param classLoader - the first class loader to try* @return the stream or null*/public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {return getResourceAsStream(resource, getClassLoaders(classLoader));}
/*** Try to get a resource from a group of classloaders** @param resource    - the resource to get* @param classLoader - the classloaders to examine* @return the resource or null*/
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {for (ClassLoader cl : classLoader) {if (null != cl) {// try to find the resource as passedInputStream returnValue = cl.getResourceAsStream(resource);// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resourceif (null == returnValue) {returnValue = cl.getResourceAsStream("/" + resource);}if (null != returnValue) {return returnValue;}}}return null;
}

我们可以从 getClassLoaders 方法中,知道传入的 ClassLoader 数组的大致内容

ClassLoader[] getClassLoaders(ClassLoader classLoader) {return new ClassLoader[]{classLoader,defaultClassLoader,Thread.currentThread().getContextClassLoader(),getClass().getClassLoader(),systemClassLoader};
}

结合上下文可以知道,数组中 classLoader 就是我们可以选择传入的自定义 ClassLoaderdefaultClassLoader 也是自定义的 ClassLoader 可以在 Resource 类中通过 set 方法传入,这两个默认值都是 nullThread.currentThread().getContextClassLoader() 这个方法返回当前线程的上下文类加载器,上下文类加载器可以被设置为任何类加载器,但默认情况下,它通常是指向应用程序类加载 AppClassLoadergetClass().getClassLoader() 这个方法返回加载当前类的类加载器,对于大多数非基本类来说,这将是应用程序类加载器 AppClassLoader,而对于 Java 核心库中的类,如 java.lang.Object,则会被引导类加载器 Bootstrap ClassLoader 加载。systemClassLoader 这个变量在当前类初始化是就被赋值了,通常是指向系统类加载器 AppClassLoader 的引用。

//systemClassLoader 的初始化
ClassLoaderWrapper() {try {systemClassLoader = ClassLoader.getSystemClassLoader();} catch (SecurityException ignored) {// AccessControlException on Google App Engine}
}

以上就是解析配置文件的基本流程,最后通过尝试调用各个类加载器的解析配置文件的方法,来对配置文件进行解析,在这个过程中,大量使用了方法重载,使得程序变得灵活

获取 SqlSessionFactory 对象管理数据库连接

在这一步骤中,首先我们可以看到 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 一句中使用 SqlSessionFactoryBuilder 类的 build 方法来构建 SqlSessionFactory 类对象,进入到这个类中可以注意到,它体现了建造者模式的思想,我们看到这个类的核心方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);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.}}
}

在这里面,核心的两句代码是

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

这里面的 parser.parse() 提供了一个 Configuration 类的对象供 SqlSessionFactory 类使用 SqlSessionFactory build(Configuration config) 方法来构造 SqlSessionFactory 对象
可以看 parse 方法的实现细节:

public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;
}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);}
}

可以看到在这里就是配置文件的解析过程,从配置文件的根节点开始,逐层进行解析,也包括其中相关的映射文件,解析过程里,不断把解析结果放入到 Configuration 对象中,最后通过这个 Configuration 对象,构造 SqlSessionFactory 对象。
最后需要注意到的是,SqlSessionFactory 本身是一个接口,最终返回的还是它的一个默认的实现类 DefaultSqlSessionFactory 的对象,代码如下:

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【前端】记录各种控制台警告/bug
  • yolo中的iou是什么意思
  • 力扣高频SQL 50题(基础版)第四十题之1164. 指定日期的产品价格
  • mysql事务与索引
  • 浅谈 Spring AOP框架 (2)——Spring统一功能处理
  • 24.8.5数据结构|栈
  • vscode ssh-remote 疑似内存泄漏问题
  • 两轮电动车行业竞争激烈,九号公司如何破局
  • uniapp点击图片预览,关闭预览图片后自动触发onshow生命周期,怎么解决?
  • Windows 环境使用 Docker 安装 ES Kibana 8.12.2 及analysis-ik插件
  • 【黑马】MyBatis
  • pythonUI自动化008::allure测试报告(安装及应用)
  • sed命令笔记
  • 基于SpringBoot+Vue校园失物招领系统的设计与实现
  • 【将Python程序打包成一个可执行文件】
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • Brief introduction of how to 'Call, Apply and Bind'
  • CSS 三角实现
  • Laravel 菜鸟晋级之路
  • node和express搭建代理服务器(源码)
  • Wamp集成环境 添加PHP的新版本
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 从tcpdump抓包看TCP/IP协议
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 深入浏览器事件循环的本质
  • 思考 CSS 架构
  • FaaS 的简单实践
  • python最赚钱的4个方向,你最心动的是哪个?
  • UI设计初学者应该如何入门?
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • # 数仓建模:如何构建主题宽表模型?
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #快捷键# 大学四年我常用的软件快捷键大全,教你成为电脑高手!!
  • (06)Hive——正则表达式
  • (19)夹钳(用于送货)
  • (2)(2.10) LTM telemetry
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (WSI分类)WSI分类文献小综述 2024
  • (转)socket Aio demo
  • ***原理与防范
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .NET Micro Framework 4.2 beta 源码探析
  • .NET 常见的偏门问题
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .Net8 Blazor 尝鲜
  • .NetCore+vue3上传图片 Multipart body length limit 16384 exceeded.
  • .NetCore项目nginx发布
  • .Net程序猿乐Android发展---(10)框架布局FrameLayout
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .pings勒索病毒的威胁:如何应对.pings勒索病毒的突袭?
  • /3GB和/USERVA开关
  • @RequestBody与@ResponseBody的使用
  • @vueup/vue-quill使用quill-better-table报moduleClass is not a constructor
  • [ vulhub漏洞复现篇 ] Hadoop-yarn-RPC 未授权访问漏洞复现
  • []AT 指令 收发短信和GPRS上网 SIM508/548