⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/b379685397/article/details/120808703 「王老狮」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

一、Mybatis运行流程概述

为了熟悉Mybatis的运行流程,我们先看一段代码。

public class MybatisDemo {
private SqlSessionFactory sqlSessionFactory;

@Before
public void init() throws IOException {
//--------------------第一步:加载配置---------------------------
// 1.读取mybatis配置文件创SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1.读取mybatis配置文件创SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
}

@Test
// 快速入门
public void quickStart() throws IOException {
//--------------------第二部,创建代理对象---------------------------
// 2.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取对应mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

//--------------------第三步:获取数据---------------------------
// 4.执行查询语句并返回单条数据
TUser user = mapper.selectByPrimaryKey(2);
System.out.println(user);

System.out.println("----------------------------------");

// 5.执行查询语句并返回多条数据
// List<TUser> users = mapper.selectAll();
// for (TUser tUser : users) {
// System.out.println(tUser);
// }
}
}

以上是我们一个使用mybatis访问数据的demo,通过对快速入门代码的分析,可以把 MyBatis 的运行流程分为三大阶段:

  1. 「初始化阶段」:读取 XML 配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化的工作;
  2. 「代理封装阶段」:封装 iBatis 的编程模型,使用 mapper 接口开发的初始化工作;
  3. 「数据访问阶段」:通过 SqlSession 完成 SQL 的解析,参数的映射、SQL 的执行、结果的解析过程;

今天我们就介绍以下第一个阶段中,Mybatis是如何读取配置的

二、配置加载的核心类

建造器三个核心类

在 MyBatis 中负责加载配置文件的核心类有三个,类图如下:

MyBatis加载配置文件核心类

  • BaseBuilder:所有解析器的父类,包含配置文件实例,为解析文件提供的一些通用的方法;
  • XMLConfigBuilder:主要负责解析 mybatis-config.xml;
  • XMLMapperBuilder:主要负责解析映射配置 Mapper.xml 文件;
  • XMLStatementBuilder:主要负责解析映射配置文件中的 SQL 节点;

XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 这三个类在配置文件加载过程中非常重要,具体分工如下图所示:

XMLConfigBuilder

这三个类使用了建造者模式对 configuration 对象进行初始化,但是没有使用建造者模式 的“肉体”(流式编程风格),只用了灵魂(屏蔽复杂对象的创建过程),把建造者模式演绎 成了工厂模式;后面还会对这三个类源码进行分析;

居然这三个对象使用的是建造者模式,那么我们稍后介绍下什么是建造者模式

三、建造者模式

什么是建造者模式

建造者模式(BuilderPattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

建造者模式类图如下:

建造者模式

各要素如下:

  • 「Product」:要创建的复杂对象
  • 「Builder」:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建;
  • 「ConcreteBuilder」:实现 Builder 接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。在建造过程完成后,提供产品的实例;
  • 「Director」:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建;

应用举例:红包的创建是个复杂的过程,可以使用构建者模式进行创建

代码示例:

1、红包对象RedPacket

public class RedPacket {
private String publisherName; //发包人
private String acceptName; //收包人
private BigDecimal packetAmount; //红包金额
private int packetType; //红包类型
private Date pulishPacketTime; //发包时间
private Date openPacketTime; //抢包时间

public RedPacket(String publisherName, String acceptName, BigDecimal packetAmount, int packetType, Date pulishPacketTime, Date openPacketTime) {
this.publisherName = publisherName;
this.acceptName = acceptName;
this.packetAmount = packetAmount;
this.packetType = packetType;
this.pulishPacketTime = pulishPacketTime;
this.openPacketTime = openPacketTime;
}

public String getPublisherName() {
return publisherName;
}

public void setPublisherName(String publisherName) {
this.publisherName = publisherName;
}

public String getAcceptName() {
return acceptName;
}

public void setAcceptName(String acceptName) {
this.acceptName = acceptName;
}

public BigDecimal getPacketAmount() {
return packetAmount;
}

public void setPacketAmount(BigDecimal packetAmount) {
this.packetAmount = packetAmount;
}

public int getPacketType() {
return packetType;
}

public void setPacketType(int packetType) {
this.packetType = packetType;
}

public Date getPulishPacketTime() {
return pulishPacketTime;
}

public void setPulishPacketTime(Date pulishPacketTime) {
this.pulishPacketTime = pulishPacketTime;
}

public Date getOpenPacketTime() {
return openPacketTime;
}

public void setOpenPacketTime(Date openPacketTime) {
this.openPacketTime = openPacketTime;
}

@Override
public String toString() {
return "RedPacket [publisherName=" + publisherName + ", acceptName="
+ acceptName + ", packetAmount=" + packetAmount
+ ", packetType=" + packetType + ", pulishPacketTime="
+ pulishPacketTime + ", openPacketTime=" + openPacketTime + "]";
}
}

2、构建对象

public class Director {
public static void main(String[] args) {
RedPacket redPacket = RedPacketBuilderImpl.getBulider()
.setPublisherName("DK").setAcceptName("粉丝")
.setPacketAmount(new BigDecimal("888")).setPacketType(1) .setOpenPacketTime(new Date())
.setPulishPacketTime(new Date()).build();
System.out.println(redPacket);
}
}

PS:流式编程风格越来越流行,如 zookeeper 的 Curator、JDK8 的流式编程等等都是例子。流式编程的优点在于代码编程性更高、可读性更好,缺点在于对程序员编码要求更高、不太利于调试。建造者模式是实现流式编程风格的一种方式;

与工厂模式区别

建造者模式应用场景如下:

  • 需要生成的对象具有复杂的内部结构,实例化对象时要屏蔽掉对象代码与复杂对象的实例化过程解耦,可以使用建造者模式;简而言之,如果“遇到多个构造器参数时要考虑用构建器”;
  • 对象的实例化是依赖各个组件的产生以及装配顺序,关注的是一步一步地组装出目标对
  • 象,可以使用建造器模式;

建造者模式与工程模式的区别在于:

「设计模式」 「形象比喻」 「对象复杂度」 「客户端参与程度」
工厂模式 生产大众版 关注的是一个产品整体,无须关心产品的各部分是如何创建出来的; 客户端对产品的创建过程参与度低,对象实例化时属性值相对比较固定;
建造者模式 生产定制版 建造的对象更加复杂,是一个复合产品,它由各个部件复合而成,部件不同产品对象不同,生成的产品粒度细; 客户端参与了产品的创建,决定了产品的类型和内容,参与度高;适合实例化对象时属性变化频繁的场景;

四、Configuration 对象介绍

实例化并初始化 Configuration 对象是第一个阶段的最终目的,所以熟悉 configuration 对 象是理解第一个阶段代码的核心;configuration 对象的关键属性解析如下:

  • MapperRegistry:mapper 接口动态代理工厂类的注册中心。在 MyBatis 中,通过mapperProxy 实现 InvocationHandler 接口,MapperProxyFactory 用于生成动态代理的实例对象;
  • ResultMap:用于解析 mapper.xml 文件中的 resultMap 节点,使用 ResultMapping 来封装id,result 等子元素;
  • MappedStatement:用于存储 mapper.xml 文件中的 select、insert、update 和 delete 节点,同时还包含了这些节点的很多重要属性;
  • SqlSource:用于创建 BoundSql,mapper.xml 文件中的 sql 语句会被解析成 BoundSql 对象,经过解析 BoundSql 包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行;

Configuration对象图解:

Configuration对象图解

需要特别注意的是 Configuration 对象在 MyBatis 中是单例的,生命周期是应用级的,换句话说只要 MyBatis 运行 Configuration 对象就会独一无二的存在;在 MyBatis 中仅在 org.apache.ibatis.builder.xml.XMLConfigBuilder.XMLConfigBuilder(XPathParser, String, Properties)中有实例化 configuration 对象的代码,如下图:

XMLConfigBuilder.XMLConfigBuilder

Configuration 对象的初始化(属性复制),是在建造 SqlSessionfactory 的过程中进行的,接下 来分析第一个阶段的内部流程;

五、配置加载流程解析

配置加载过程

可以把第一个阶段配置加载过程分解为四个步骤,四个步骤如下图:

MyBatis配置加载过程

第一步:通过 SqlSessionFactoryBuilder 建造 SqlSessionFactory,并创建 XMLConfigBuilder 对象读取 MyBatis 核心配置文件 , 见源码方法 : org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Reader, String, Properties)

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());//解析配置文件得到configuration对象,并返回SqlSessionFactory
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

第二步:进入 XMLConfigBuilder 的 parseConfiguration 方法,对 MyBatis 核心配置文件的各个元素进行解析,读取元素信息后填充到 configuration 对象。在 XMLConfigBuilder 的 mapperElement()方法中通过 XMLMapperBuilder 读取所有 mapper.xml 文件;见方法: org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XNode)

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 first
//解析<properties>节点
propertiesElement(root.evalNode("properties"));
//解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
//解析<plugins>节点
pluginElement(root.evalNode("plugins"));
//解析<objectFactory>节点
objectFactoryElement(root.evalNode("objectFactory"));
//解析<objectWrapperFactory>节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析<reflectorFactory>节点
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);//将settings填充到configuration
// read it after objectFactory and objectWrapperFactory issue #631
//解析<environments>节点
environmentsElement(root.evalNode("environments"));
//解析<databaseIdProvider>节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析<typeHandlers>节点
typeHandlerElement(root.evalNode("typeHandlers"));
//解析<mappers>节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}

第三步:XMLMapperBuilder 的核心方法为 configurationElement(XNode),该方法对 mapper.xml 配置文件的各个元素进行解析,读取元素信息后填充到 configuration 对象。

private void configurationElement(XNode context) {
try {
//获取mapper节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置builderAssistant的namespace属性
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref节点
cacheRefElement(context.evalNode("cache-ref"));
//重点分析 :解析cache节点----------------1-------------------
cacheElement(context.evalNode("cache"));
//解析parameterMap节点(已废弃)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//重点分析 :解析resultMap节点----------------2-------------------
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
//重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
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);
}
}

在 XMLMapperBuilder 解析过程中,有四个点需要注意:

  1. resultMapElements(List)方法用于解析 resultMap 节点,这个方法非常重要, 一定要跟源码理解;解析完之后数据保存在 configuration 对象的 resultMaps 属性中;如下图

XMLMapperBuilder 解析过程

  1. 2XMLMapperBuilder 中在实例化二级缓存(见 cacheElement(XNode))、实例化 resultMap (见 resultMapElements(List))过程中都使用了建造者模式,而且是建造者模 式的典型应用;
  2. XMLMapperBuilder 和 XMLMapperStatmentBuilder 有 自 己 的 “ 秘 书 ” MapperBuilderAssistant。XMLMapperBuilder 和 XMLMapperStatmentBuilder 负责解析 读取配置文件里面的信息,MapperBuilderAssistant 负责将信息填充到 configuration。将文件解析和数据的填充的工作分离在不同的类中,符合单一职责原则;
  3. 在 buildStatementFromContext(List)方法中,创建 XMLStatmentBuilder 解析 Mapper.xml 中 select、insert、update、delete 节点

第四步:在 XMLStatmentBuilder 的 parseStatementNode()方法中,对 Mapper.xml 中 select、 insert、update、delete 节点进行解析,并调用 MapperBuilderAssistant 负责将信息填充到 configuration。在理解 parseStatementNod()方法之前,有必要了解 MappedStatement,这个类用于封装 select、insert、update、delete 节点的信息;如下图所示:

XMLStatmentBuilder

至此,整个Mybatis的配置即加载完毕,整个加载流程图如下:

Mybatis的配置加载流程图

文章目录
  1. 1. 一、Mybatis运行流程概述
  2. 2. 二、配置加载的核心类
    1. 2.1. 建造器三个核心类
  3. 3. 三、建造者模式
    1. 3.1. 什么是建造者模式
    2. 3.2. 与工厂模式区别
  4. 4. 四、Configuration 对象介绍
  5. 5. 五、配置加载流程解析
    1. 5.1. 配置加载过程