{T}

MyBatis 架构与执行流程分析

架构分析

Mybatis 是一个非常小巧灵活的 ORM框架,是为了消除所有的 JDBC 代码和参数的手工设置以及结果集的封装问题;基于这个一点知道MyBatis 实际上就是对 JDBC 的封装

Mybatis 架构设计图

接口层

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

传统的方式

下面的这些方法就是在 SqlSession 接口中提供的接口方法

java
/**
   * Retrieve a single row mapped from the statement key and parameter.
   * @param <T> the returned object type
   * @param statement Unique identifier matching the statement to use.
   * @param parameter A parameter object to pass to the statement.
   * @return Mapped object
   */
<T> T selectOne(String statement, Object parameter);

/**
   * Retrieve a list of mapped objects from the statement key and parameter.
   * @param <E> the returned list element type
   * @param statement Unique identifier matching the statement to use.
   * @param parameter A parameter object to pass to the statement.
   * @return List of mapped object
   */
<E> List<E> selectList(String statement, Object parameter);

/**
   * Execute an insert statement with the given parameter object. Any generated
   * autoincrement values or selectKey entries will modify the given parameter
   * object properties. Only the number of rows affected will be returned.
   * @param statement Unique identifier matching the statement to execute.
   * @param parameter A parameter object to pass to the statement.
   * @return int The number of rows affected by the insert.
   */
int insert(String statement, Object parameter);

/**
   * Execute an update statement. The number of rows affected will be returned.
   * @param statement Unique identifier matching the statement to execute.
   * @param parameter A parameter object to pass to the statement.
   * @return int The number of rows affected by the update.
   */
int update(String statement, Object parameter);

/**
   * Execute a delete statement. The number of rows affected will be returned.
   * @param statement Unique identifier matching the statement to execute.
   * @param parameter A parameter object to pass to the statement.
   * @return int The number of rows affected by the delete.
   */
int delete(String statement, Object parameter);
// 此处省略一万字....

Mapper 代理方式

Mapper 代理的方式就是通过创建接口(UserMapper 接口)的代理对象调用接口中的方法,最终执行方法调用是 Executor 对象,底层其实还是调用的传统的 API 方法

数据处理层

数据处理层这部分主要就 JDBC 核心内容,包括参数映射、类型转换、SQL 解析、SQL 执行以及结果的封装,这部分内容就可以理解成JDBC 的核心内容,这里有几个重要类

  • ParameterHandler:SQL 的参数处理
  • ResultSetHandler:结果集处理
  • StatementHandler: 封装了 JDBC Statement 操作,设置参数,转换结果集
  • ExecutorMyBatis 执行器,用于执行增删改查操作

框架支撑层

这部分主要是框架抽取出来的通用组件包括数据源管理、事务管理、配置加载和缓存处理;为上层的数据处理层提供最基础的支撑

数据源管理

数据源的管理对于 持久层 框架来说,可谓是非常的重要,稍有不慎就会浪费大量系统资源,MyBatis 也对此做了很好的支持,MyBatis有三种内建的数据源类型 UNPOOLEDPOOLEDJNDI

  • UNPOOLED– 数据源的实现会每次请求时打开和关闭连接
  • POOLED– 数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间
  • JNDI –个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI上下文的数据源引用。这种已经不用了

通常情况下是会使用 POOLED ,可以节省很多宝贵的系统资源

事务管理 transactionManager

MyBatis 中有两种类型的事务管理器 ( JDBC / MANAGED ) 它通过一个顶层的 Transaction 接口以及其不同实现 JdbcTransactionManagedTransaction 来实现对事务的管理:

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域,对应的实现类JdbcTransaction
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文),对应的实现类ManagedTransaction

对事务的管理,在和 Spring 整合之后,通常会用 Spring 的事务管理器

缓存处理

缓存对于持久层框架来说还是非常重要的,一定程度上可以减少和数据库的交互,MyBatis 提供了两种缓存机制,一级缓存和二级缓存

  • 一级缓存:是SqlSession级别的缓存,也就是会话级别的,如果两个相同的查询,第二次的查询会直接先从缓存中去拿,以及缓存也是默认开启的,这部分在源码分析的内容里会详细分析
  • 二级缓存:是 Mapper 级别的缓存,也就是一个xxx.xml 内的查询是可以共用的,需要手动开启

SQL 解析

MyBatis 支持两种 SQL 解析的方式 一种 xml、一种是注解;

两种方式对于一些基础的 CRUD 区别不大,使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句

引导层

MyBatis 启动时核心配置文件的方式,严格来说也可以不算在架构层面,不过 MyBatis 也是提供了两种方式一个是 xml 的方式,也是用的比较多的,一种是使用 Java API 的方式

xml 方式

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
    <!--省略-->
    </environment>
  </environments>
</configuration>

Java API 方式

code
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);

一般情况下还是使用 xml 的比较方便

执行流程分析

核心流程

MyBtis 是对 JDBC 的封装,JDBC 执行六部走

  1. 注册驱动
  2. 获取 Connection 连接
  3. 执行预编译
  4. 执行 SQL
  5. 封装结果集
  6. 释放资源

分析下 MyBatis 是怎样来封装这个过程的

### 执行流程分析

  1. 读取 MyBatis 的核心配置文件。mybatis-config.xml 是 MyBatis 全局配置文件,用于配置数据库连接、属性、类型别名、类型处理器、插件、环境配置、映射器(mapper.xml)等信息,这个核心配置文件最终会被封装成一个 Configuration 对象

  2. 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,映射文件是在 mybatis-config.xml 中加载;可以加载多个映射文件,每个文件对应数据库中的一张表。

    配置的方式有两种

    xml
    <!-- 使用包路径,扫描包下所有的接口,这种方式比较方便 -->
    <package name="com.mybatis.demo"/>
    
    <!-- resource:使用相对路径的资源引用-->
    <!-- url:使用绝对类路径的资源引用-->
    <!-- class:使用映射器接口实现类的完全限定类名-->
    <mapper resource="xxx.xml"/>
  3. 构造会话工厂获取 SqlSessionFactory 。这个过程是用建造者设计模式使用 SqlSessionFactoryBuilder 对象构建的,SqlSessionFactory 的最佳作用域是应用作用域

  4. 创建会话对象 SqlSession。由会话工厂创建 SqlSession 对象,对象中包含了执行 SQL 语句的所有方法,每个线程都应该有它自己的 SqlSession 实例。SqlSession 实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

  5. Executor 执行器是 MyBatis 的核心,负责 SQL 语句的生成和查询缓存的维护,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护

    • SimpleExecutor – SIMPLE 就是普通的执行器
    • ReuseExecutor -执行器会重用预处理语句(PreparedStatements
    • BatchExecutor --它是批处理执行器
  6. MappedStatement 对象是对解析的 SQL 的语句封装,一个 MappedStatement 代表了一个 sql 语句标签

    xml
    <select id="selectUserList" resultType="com.mybatis.User">    
      select * from t_user
    </select>
  7. 输入参数映射。输入参数类型可以是基本数据类型,也可以是 MapListPOJO 类型复杂数据类型,这个过程类似于 JDBC 的预编译处理参数的过程

  8. 封装结果集。可以封装成多种类型可以是基本数据类型,也可以是 MapListPOJO 类型复杂数据类型。封装结果集的过程就和JDBC封装结果集是一样的

这样一对比,就可以看出了其实 Mybatis 的执行流程和 JDBC 其实大同小异。如果用代码表示如下:

java
// 1.加载配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建SqlSessionFactory对象实际创建的是DefaultSqlSessionFactory对象
SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 创建SqlSession对象实际创建的是DefaultSqlSession对象
SqlSession sqlSession = builder.openSession();
// 4. 创建代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 5. 执行查询语句
List<User> users = mapper.selectUserList();

总结 MyBatis 执行流程

  • 加载解析配置文件
  • 处理参数
  • 执行查询
  • 封装结果集

版本差异(MyBatis 3.x → 3.5.x)

特性旧版(3.4.x 及以前)当前(3.5.x)
JDK 支持JDK 6+JDK 8+(3.5.13+ 兼容虚拟线程)
动态 SQL基本 OGNL支持 if 多条件、trim、foreach 增强
参数/结果映射普通 POJO支持 record 构造器自动映射(3.5.13+)
插件机制Interceptor 拦截 StatementHandler 等不变,新增 @Intercepts 校验增强
延迟加载需配置默认支持,可配置 aggressiveLazyLoading

整体架构(SqlSessionFactory → SqlSession → Executor → MappedStatement → StatementHandler)与执行流程在 3.5.x 中保持不变,重点在于 JDK 兼容性与 record 等新类型支持。