MyBatis核心源码深度剖析工作机制和实现原理

Posted 赵广陆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis核心源码深度剖析工作机制和实现原理相关的知识,希望对你有一定的参考价值。

目录


1 MyBatis源码分析导入

1.1 为什么要看MyBatis框架的源码

MyBatis 是一款优秀的持久层框架,也是当前最流行的java持久层框架之一,它内部封装了jdbc,使
开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中
sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并
返回。
为了更好地学习和理解mybatis背后的设计思路,作为高级开发人员,有必要深入研究了解优秀框架的源
码,以便更好的借鉴其思想。同时,框架作为设计模式的主要应用场景,通过研究优秀框架的源码,可以更好的领会设计模式的精髓。

通过学习开源框架MyBatis的源码,我们可以深入学习到框架的解析思路和底层的实现原理,掌握源码的
剖析办法,快速增加查看源码的经验;
1)在使用MyBatis框架进行开发时,如果你对其源码有所了解,可以最大化地减少出故障的可能;
2)学习源码分析的最大好处是可以开阔思维,提升架构设计能力,通过看源码,看别人如何设计,然后领悟到这样设计的好处,理解优秀的代码设计思想;
3)互联网大厂对有经验的开发人员的招聘,对架构思想和底层的理解能力考察方面比较重视,学习完有助于提高自己的竞争力;
4)可以在深入的学习、剖析后,可以对框架进行改造,进而自定义MyBatis框架,提升架构能力。

1.2 如何深入学习MyBatis源码

1)查看MyBatis官方文档;
https://mybatis.org/mybatis-3/zh/index.html

2)断点跟进源码,参照主线,一步步分析;

3)手动自定义MyBatis框架,加深对框架源码的理解,掌握源码的学习方法,进而提升自身架构能力;

1.3 源码分析的5大原则

1)紧跟入口
2)看图梳理
3)先粗后细
4)精略结合
5)猜想验证

2 MyBatis架构体系深入剖析

2.1 MyBatis的整体架构体系

2.2 MyBatis的工作机制和实现原理

1)接口层
2)数据处理核心层
3)基础支撑层

编码时从下到上进行执行

(1)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。

(2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

(3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑

JDBC代码回顾:

/**
* JDBC开发示例代码
*/
public class JDBCTest 
  private static Logger logger = Logger.getLogger(JDBCTest.class);
  public static void main(String[] args) throws Exception 
    // 1、注册驱动
    DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    // 2、建立连接
    Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis_indepth?
useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT", "root", "root");
    // 3、编写sql,进行预编译
    String sql = " select * from user;";
    PreparedStatement ps = con.prepareStatement(sql);
    // 4、执行查询,得到结果集
    ResultSet rs = ps.executeQuery();
    while (rs.next()) 
      int id = rs.getInt("id");
      String name = rs.getString("name");
      logger.info("====> id=" + id + "\\tname=" + name);
   
    //5、关闭事务
    rs.close();
    ps.close();
    con.close();
 

mybatis代码:

InputStream in;
SqlSessionFactoryBuilder builder;
SqlSessionFactory factory;
SqlSession session;
UserMapper userMapper = null;
@Before
public void init() throws Exception 
  //1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
  //2.创建SqlSessionFactory工厂
  builder = new SqlSessionFactoryBuilder();
  factory = builder.build(in);
  //3.使用工厂生产SqlSession对象
  session = factory.openSession();
  //4.使用SqlSession创建Dao接口的代理对象
  userMapper = session.getMapper(UserMapper.class);

@After
public void after() throws IOException 
  //6.释放资源
  session.close();
  in.close();

/**
* 入门案例
*/
@Test
public void testFindById() throws Exception 
  //5.使用代理对象执行方法
  User user = userMapper.findUserById(1);
  System.out.println(user);

思考:mybatis为我们做了什么?
mybatis如何获取数据源连接信息?
mybatis如何获取到需要执行的sql语句?
mybatis是如何完成sql执行的?
mybatis如何完成参数映射和结果封装?

2.2.1 接口层

概述:对应 session 模块。
接口层相对简单,其核心是 SqlSession 接口,该接口中定义了 MyBatis 暴露给应用程序调用的
API,也就是上层应用与 MyBatis 交互的桥梁。接口层在接收到调用请求时,会调用核心处理层的相
应模块来完成具体的数据库操作。
作用:
使用SqlSession接口和Mapper接口通知调用哪个sql还有关联参数。
可以实现数据的增/删/改/查接口 配置信息维护接口,进行动态的更改配置

2.2.1.1 获取SqlSession流程分析

Mybatis解析完配置文件后,会生成一个DefaultSqlSessionFactory对象,调用openSession方法,即可获得一个SqlSession(使用的是默认的DefaultSqlSession对象)。

2.2.1.2 SqlSession源码分析

作用:Mybatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能

2.2.2 数据处理核心层

在核心处理层中,实现了 MyBatis 的核心处理流程。
其中包括 MyBatis 的初始化以及完成一次数据库操作的涉及的全部流程 。

2.2.2.1 配置解析(参数映射)

概述: 对应builder 和 mapping模块。前者为配置解析过程,后者主要为 SQL 操作解析后的映射。在 MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接
口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。利用该Configuration 对象创建 SqlSessionFactory对象。待 MyBatis 初始化之后,开发人员可以通
过初始化得到 SqlSessionFactory 创建 SqlSession 对象并完成数据库操作。

Configuration 概述:是一个所有配置信息的容器对象 实战分析:Configuration对象涉及到的配置信息
分析

简单的理解:MyBatis初始化的过程,就是创建 Configuration对象,加载各种配置信息的过程

2.2.2.2 SQL解析(SqlSource)

概述: 对应 scripting 模块。
MyBatis 中的 scripting 模块,会根据用户传入的实参,解析映射文件中定义的动态 SQL 节点,并形成数据库可执行的 SQL 语句。之后会处理 SQL 语句中的占位符,绑定用户传入的实参
负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并
返回。

实战分析: SqlSource 接口继承体系

各个实现类分析:
RawSqlSource 负责处理静态 SQL 语句,它们最终会把处理后的 SQL 封装 StaticSqlSource 进
行返回。StaticSqlSource 处理包含的 SQL 可能含有 “?” 占位符,可以被数据库直接执行。
DynamicSqlSource 负责处理动态 SQL 语句。

ProviderSqlSource 实现 SqlSource 接口,基于方法上的 @ProviderXXX 注解的 SqlSource 实
现类。

2.2.2.3 SQL执行(Executor)

概述:对应 executor 模块
是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
SQL 语句的执行涉及多个组件 ,其中比较重要的是 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 。
Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关
操作委托给 StatementHandler完成。StatementHandler 首先通过 ParameterHandler 完成 SQL 语句的实参绑定,然后通过java.sql.Statement 对象执行 SQL 语句并得到结果集,最后通过 ResultSetHandler 完成结果集的映射,得到结果对象并返回。

2.2.3 基础支撑层

概述:基础支持层,包含整个 MyBatis 的基础模块,这些模块为核心处理层的功能提供了良好的支
撑。
日志:对应 logging 包概述:Mybatis提供了详细的日志输出信息,还能够集成多种日志框架,其日志模块的主要功能就是集成第三方日志框架。设计模式分析使用的适配器模式分析

缓存机制:对应 cache 包

一级缓存
概述:Session或Statement作用域级别的缓存,默认是Session,BaseExecutor中根据
MappedStatement的Id、SQL、参数值以及rowBound(边界)来构造CacheKey,并使用BaseExccutor中
的localCache来维护此缓存。实战应用场景分析默认开启的缓存

二级缓存
概述:全局的二级缓存,通过CacheExecutor来实现,其委托TransactionalCacheManager来保
存/获取缓存实战应用场景分析缓存的效率以及应用场景注意点:两级缓存与Mybatis以及整个应用是运行在同一个JVM中的,共享同一块内存,如果这两级缓存中的数据量较大,则可能影响系统中其它功能,需要缓存大量数据时,优先考虑使用Redis、Memcache等缓存产品。

数据源/连接池:对应 datasource 包
概述:Mybatis自身提供了相应的数据源实现,也提供了与第三方数据源集成的接口。
分析:主要实现类是PooledDataSource,包含了最大活动连接数、最大空闲连接数、最长取出时间(避免某个线程过度占用)、连接不够时的等待时间。
实战应用:连接池、检测连接状态等,选择性能优秀的数据源组件,对于提供ORM框架以及整个应用的性能都是非常重要的。

事务管理:对应 transaction 包
概述:Mybatis自身对数据库事务进行了抽象,提供了相应的事务接口和简单实现。
注意点:一般地,Mybatis与Spring框架集成,由Spring框架管理事务。

反射:对应 reflection 包
概述:对Java原生的反射进行了很好的封装,提供了简易的API,方便上层调用,并且对反射操作进行了一系列的优化,提高了反射操作的性能。
实战应用
① 缓存了类的元数据(MetaClass)
② 对象的元数据(MetaObject)
IO 模块: 对应 io 包。

资源加载模块,主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能 。

解析器: 对应 parsing 包
解析器模块,主要提供了两个功能:
1.对 XPath 进行封装,为 MyBatis 初始化时解析 mybatis-config.xml 配置文件以及映射配置
文件提供支持。
2.为处理动态 SQL 语句中的占位符提供支持。

2.3 MyBatis的核心配置文件解析原理

在 MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接 口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中

2.3.1 解析的目的

概述:通过资源类Resources读入“SqlMapConfig.xml”文件 使用SqlSessionFactoryBuilder类生成我们需
要的SqlSessionFactory类。 目的:

使用“SqlMapConfig.xml”将数据库连接参数单独配置在 db.properties 中,只需要在 SqlMapConfig.xml 中加载该配置文件的属性值。在 SqlMapConfig.xml就不需要对数据库连接参数硬编码。将数据库连接参数只配置在 db.properties 中,原因:方便对参数进行统一管理,其他 xml 可以应用该配置文件。

mybatis解析配置文件最本质的目的是为了获得Configuration对象;然后,利用该 Configuration 对象创建 SqlSessionFactory对象。待 MyBatis 初始化之后,可以通过初始化得到 SqlSessionFactory 创建 SqlSession 对象并完成数据库操作。

2.3.2 XML 解析流程

2.3.2.1 入口

MyBatis 的初始化流程的入口是 SqlSessionFactoryBuilder 的 build 方法:

/**
* 构造 SqlSessionFactory 对象
*
* @param reader Reader 对象
* @param environment 环境
* @param properties Properties 变量
* @return SqlSessionFactory 对象
*/
public SqlSessionFactory build(Reader reader, String environment, Properties
properties) 
  try 
    // <1> 创建 XMLConfigBuilder 对象
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment,properties);
    // <2> 执行 XML 解析
    // <3> 创建 DefaultSqlSessionFactory 对象
    return build(parser.parse());
  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.
   
 

2.3.2.2 XMLConfigBuilder

org.apache.ibatis.builder.xml.XMLConfigBuilder ,继承 BaseBuilder 抽象类,XML 配置构建器;主要负责解析 mybatis-config.xml 配置文件:

//【1.构造设置Properties】
private XMLConfigBuilder(XPathParser parser, String environment, Properties
props) 
  // <1> 创建 Configuration 对象
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  // <2> 设置 Configuration 的 variables 属性
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;

// parse【2. 判断是否解析过】
public Configuration parse() 
  // <1.1> 若已解析,抛出 BuilderException 异常
  if (parsed) 
  throw new BuilderException("Each XMLConfigBuilder can only be used
once.");
 
  // <1.2> 标记已解析
  parsed = true;
  // <2> 解析 XML configuration 节点
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;

//parseConfiguration 【3. 解析configuration节点】
private void parseConfiguration(XNode root) 
  try 
    //issue #117 read properties first
    // <1> 解析 <properties /> 标签
    propertiesElement(root.evalNode("properties"));
    // <2> 解析 <settings /> 标签
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    // <3> 加载自定义 VFS 实现类
    loadCustomVfs(settings);
    // <4> 解析 <typeAliases /> 标签
    typeAliasesElement(root.evalNode("typeAliases"));
    // <5> 解析 <plugins /> 标签
    pluginElement(root.evalNode("plugins"));
    // <6> 解析 <objectFactory /> 标签
    objectFactoryElement(root.evalNode("objectFactory"));
    // <7> 解析 <objectWrapperFactory /> 标签
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    // <8> 解析 <reflectorFactory /> 标签
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // <9> 赋值 <settings /> 到 Configuration 属性
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    // <10> 解析 <environments /> 标签
    environmentsElement(root.evalNode("environments"));
    // <11> 解析 <databaseIdProvider /> 标签
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // <12> 解析 <typeHandlers /> 标签
    typeHandlerElement(root.evalNode("typeHandlers"));
    // <13> 解析 <mappers /> 标签
    mapperElement(root.evalNode("mappers"));
  catch (Exception e) 
    throw new BuilderException("Error parsing SQL Mapper Configuration.
Cause: " + e, e);
 

new Configuration()
配置文件解析的本质就是获得Configuration对象 很多需要的成员变量需要根据 XML 配置文件解析后
来赋值

parser.parse()
该函数就是XML解析的核心 解析全局配置文件,调用parse.evalNode()方法,将指定路径的config配
置文件转换为XNode对象 调用parseConfiguration()方法逐步解析配置文件中的各个节点

build(configuration)
该函数用来创建一个具体的SqlSessionFactory对象。 创建DefaultSqlSessionFactory对象, 并将
configuration赋值给相应的成员变量

2.3.3 核心解析逻辑:

org.apache.ibatis.parsing.XPathParser ,基于 Java XPath 解析器,用于解析 MyBatis mybatis-config.xml 和 **Mapper.xml 等 XML 配置文件。属性如下:

/**
* XML Document 对象
*/
private final Document document;
/**
* 是否校验
*/
private boolean validation;
/**
* XML 实体解析器
*/
private EntityResolver entityResolver;
/**
* 变量 Properties 对象
*/
private Properties variables;
/**
* Java XPath 对象
*/
private XPath xpath;
/**
* 构造 XPathParser 对象
*
* @param xml XML 文件地址
* @param validation 是否校验 XML
* @param variables 变量 Properties 对象
* @param entityResolver XML 实体解析器
*/
public XPathParser(String xml, boolean validation, Properties variables,
EntityResolver entityResolver) 
  commonConstructor(validation, variables, entityResolver);
  this.document = createDocument(new InputSource(new StringReader(xml)));

/**
公用的构造方法逻辑
*/
private void commonConstructor(boolean validation, Properties variables,
EntityResolver entityResolver) 
  this.validation = validation;
  this.entityResolver = entityResolver;
  this.variables = variables;
  // 创建 XPathFactory 对象
  XPathFactory factory = XPathFactory.newInstance();
  this.xpath = factory.newXPath();

/**
* 创建 Document 对象 --->将 XML 文件解析成 Document 对象
*
* @param inputSource XML 的 InputSource 对象
* @return Document 对象
*/
private Document createDocument(InputSource inputSource) 
  // important: this must only be called AFTER common constructor
  try 
    // 1> 创建 DocumentBuilderFactory 对象
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(validation); // 设置是否验证 XML
    factory.setNamespaceAware(false);
    factory.setIgnoringComments(true);
    factory.setIgnoringElementContentWhitespace(false);
    factory.setCoalescing(false);
    factory.setExpandEntityReferences(true);
    // 2> 创建 DocumentBuilder 对象
    DocumentBuilder builder = factory.newDocumentBuilder();
    builder.setEntityResolver(entityResolver); // 设置实体解析器
    builder.setErrorHandler(new ErrorHandler()  // 实现都空的
      @Override
      public void error(SAXParseException exception) throws SAXException 
        throw exception;
     
      @Override
      public void fatalError(SAXParseException exception) throws
SAXException 
        throw exception;
     
      @Override
      public void warning(SAXParseException exception) throws SAXException

     
   );
    // 3> 解析 XML 文件
    return builder.parse(inputSource);
     catch (Exception e) 
    throw new BuilderException("Error creating document instance. Cause: "
+ e, e);
 

org.apache.ibatis.builder.xml.XMLMapperEntityResolver ,实现 EntityResolver 接口,
MyBatis 自定义 EntityResolver 实现类,用于加载本地的 mybatis-3-config.dtd 和 mybatis-3-mapper.dtd 这两个 DTD 文件。代码如下:

public class XMLMapperEntityResolver implements EntityResolver 
  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
  /**
  * 本地 mybatis-config.dtd 文件
  */
  private static final String MYBATIS_CONFIG_DTD =
"org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  /**
  * 本地 mybatis-mapper.dtd 文件
  */
  private static final String MYBATIS_MAPPER_DTD =
"org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
  /**
  * Converts a public DTD into a local one
  */
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws
SAXException 
    try 
      if (systemId != null) 
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        // 本地 mybatis-config.dtd 文件
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) ||
lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) 
          return getInputSource(MYBATIS_CONFIG_DTD, publicId,
systemId);
        // 本地 mybatis-mapper.dtd 文件
 else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) ||
lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) 
          return getInputSource(MYBATIS_MAPPER_DTD, publicId,
systemId);
       
     
      return null;
    catch (Exception e) 
      throw new SAXException(e.toString());
   
 
  private InputSource getInputSource(String path, String publicId, String
systemId) MyBatis核心源码深度剖析SQL执行过程

深入浅出Spring原理及实战「原理分析专题」不看源码就带你剖析AOP容器核心流程以及运作原理

史上最全Android渲染机制讲解(长文源码深度剖析)

云原生网关 APISIX 的核心流程以源码分析的方式剖析其工作原理

唯一插件化Replugin源码及原理深度剖析--初始化之框架核心

Spring源码剖析依赖注入实现