Java-16 深入浅出 MyBatis - SqlSession Executor StatementHandler 源码分析

点一下关注吧!!!非常感谢!!持续更新!!!

大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html

# 目前已经更新到了:

  • MyBatis(正在更新)

架构设计

把 MyBatis 的功能架构分为三层:

  • API 接口层:提供给外部使用的接口 API,开发人员通过这些本地 API 来操作数据库,接口层收到调用请求就会调用数据处理层来完成具体的数据处理。(MyBatis 与数据库的交互有两种方法,通过 MyBatis 的 API、通过 Mapper 代理的方式)
  • 数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用请求完成一次数据库的操作。
  • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

主要构件

层次结构

总体流程

加载配置初始化

触发条件:加载配置文件

配置来源于两个地方,一个是配置文件(主配置文件 config.xml、mapper.xml),一个是 Java 代码中的注解,将主配置内容解析封装到 Configuration,将 SQL 的配置信息加载为一个 mappedstatement 对象,存储在内存之中。

接受调用请求

  • 触发条件:调用 MyBatis 的 API
  • 传入参数:为 SQL 的 ID 和传入参数对象
  • 处理过程:将请求传递给下层的请求处理层进行处理

处理操作请求

触发条件:API 接口层传递请求过来

传入参数:为 SQL 的 ID 和传入参数对象

处理过程:

  • 根据 SQL 的 ID 查找对应的 MappedStatement 对象
  • 根据传入参数对象解析 MappedStatement 对象,得到最终要执行的 SQL 和执行传入的对象
  • 获取数据库连接,根据得到的最终 SQL 语句和执行传入参数到数据库进行执行,并得到结果。
  • 根据 MappedStatement 对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果。
  • 释放连接资源
  • 返回处理结果

源码剖析

初始化

java 复制代码
Inputstream inputstream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

初始化的 build 方法中,如下:

java 复制代码
// 1. 最初调用的 build 方法
public SqlSessionFactory build(InputStream inputStream) {
    // 调用了重载方法,传入默认的环境和属性
    return build(inputStream, null, null);
}

// 2. 重载的 build 方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // XMLConfigBuilder 负责解析 MyBatis 的配置文件
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        
        // 解析配置文件并返回 Configuration 对象,再调用重载的 build 方法
        return build(parser.parse());
    } catch (Exception e) {
        // 异常处理:将异常包装成一个运行时异常并抛出
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    }
}

MyBatis 在初始化的时候,会将 MyBatis的配置信息全部加载到内存中,使用 org.apache.ibatis.session.Configuration 实例来维护。

java 复制代码
/**
 * 解析 XML 配置并转换为 Configuration 对象。
 */
public Configuration parse() {
    // 如果已解析,抛出 BuilderException 异常
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }

    // 标记为已解析
    parsed = true;

    // 解析 XML 配置根节点
    parseConfiguration(parser.evalNode("/configuration"));

    return configuration;
}

/**
 * 解析 XML 配置节点并逐一处理各个标签。
 */
private void parseConfiguration(XNode root) {
    try {
        // 按顺序解析各个 XML 配置部分

        // 1. 解析 <properties /> 标签
        propertiesElement(root.evalNode("properties"));

        // 2. 解析 <settings /> 标签并转换为 Properties 对象
        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);

        // 10. 解析 <environments /> 标签
        environmentsElement(root.evalNode("environments"));

        // 11. 解析 <databaseIdProvider /> 标签
        databaseldProviderElement(root.evalNode("databaseldProvider"));

        // 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);
    }
}

介绍一下 MappedStatement:MappedStatement 与 Mapper 配置文件中的一个select/update/insert/delete 节点相对应。

mapper 中配置的标签都被封装到了此对象中,主要用途是描述一条 SQL 语句。

刚开始介绍配置文件的过程中,会对 mybatis-config.xml 中的各个标签都进行解析,其中有 mappers 标签用引入 mapper.xml 文件或者配置 mapper 接口目录。

xml 复制代码
<select id="getUser" resultType="user" >
  select * from user where id=#{id}
</select>

一个 Select 标签会在初始化配置文件时,被解析封装成一个 MappedStatement 对象,然后存储在 Configuration 对象的 MappedStatements 属性中,它是一个 HashMap,存储时 key=全限定类名+方法名,value=MappedStatement 对象。

在 XMLConfigBuilder 中的处理:

java 复制代码
private void parseConfiguration(XNode root) {
    try {
        // 省略其他标签的处理
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

到此 XML 配置文件的解析就结束了,回到步骤 2 中调用重载的 build 方法。

java 复制代码
public SqlSessionFactory build(Configuration config) {
    // 创建了 DefaultSqlSessionFactory 对象
    return new DefaultSqlSessionFactory(config);
}

SqlSession

先简单介绍 SqlSession,SqlSession 是一个接口,它有两个实现类:

  • DefaultSqlSession
  • SqlSessionManager

SqlSession 是 MyBatis 中用于和数据库交互的顶层类,通常将它与 ThreadLocal 绑定,一个回话使用一个 SqlSession,结束之后需要进行 close。

java 复制代码
public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;
    // 省略
}

SqlSession 中最重要的参数:

  • Configuration 与初始化的时候相同
  • Executor 为执行器

Executor

Executor 也是一个接口,有三个常用实现类:

  • BatchExecutor 重用语句并执行批量更新
  • ReuseExecutor 重用预处理语句 prepared statments
  • SimpleExecutor 普通的执行器(默认的)

当我们获取到 SqlSession 的时候:

java 复制代码
// 6. 进入 openSession 方法。
public SqlSession openSession() {
    // getDefaultExecutorType() 传递的是 SimpleExecutor
    // 通过 openSessionFromDataSource 方法创建 SqlSession
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

// 7. 进入 openSessionFromDataSource 方法。
// ExecutorType 为 Executor 的类型,TransactionIsolationLevel 为事务隔离级别,autoCommit 是否开启事务
// openSession 的多个重载方法可以指定获得的 SqlSession 的 Executor 类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType,
                                            TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // 获取当前环境配置
        final Environment environment = configuration.getEnvironment();
        
        // 获取事务工厂
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        
        // 创建事务对象,传入数据源、事务隔离级别和是否自动提交
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        
        // 根据指定的 Executor 类型和事务创建 Executor
        final Executor executor = configuration.newExecutor(tx, execType);
        
        // 返回一个 DefaultSqlSession 对象,传入配置、executor 和 autoCommit 状态
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        // 捕获异常,关闭事务,可能已经获取了数据库连接,因此需要关闭
        closeTransaction(tx);
        // 重新抛出异常,或处理异常逻辑
        throw new RuntimeException("Error occurred while opening session", e);
    }
}

后续可跟进 query 方法,最后会创建一个 StatementHandler 对象,然后将必要的参数传递给 StatementHandler,使用 StatementHandler 来完成对数据库的查询,最终返回 List 结果集。

从上面的代码中我们可以看出,Executor 的功能和作用是:

  • 根据传递的参数,完成 SQL 的动态解析,生成 Bound SQL 对象,供 StatementHandler 使用
  • 为查询创建缓存,来提高性能
  • 创建 JDBC 的 Statement 连接对象,传递给 StatementHandler 对象,返回 List 查询对象。

StatementHandler

StatementHandler 对象主要完成了两个工作:

  • 对于 JDBC 的 PreparedStatement 类型的对象,创建的过程中,我们使用的是 SQL 语句字符串会包含若干个占位符,StatementHandler 通过 parameter(statement)方法对 Statement 进行设值
  • StatementHandler 通过 List query 方法来完成执行 Statement,将 Statement 对象返回的 resultSet 封装成 List

进入到 StatementHandler 的 parameterize 方法的实现:

java 复制代码
public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
}
相关推荐
漂流瓶6666665 分钟前
Scala的模式匹配变量类型
开发语言·后端·scala
夏天吃哈密瓜11 分钟前
Scala中的正则表达式01
大数据·开发语言·后端·正则表达式·scala
2401_8337880513 分钟前
Scala的模式匹配(2)
java·开发语言
Lbs_gemini060313 分钟前
C++研发笔记14——C语言程序设计初阶学习笔记12
c语言·开发语言·c++·笔记·学习
ac-er888837 分钟前
GD库如何根据颜色生成纯色背景图
开发语言·php
悠悠龙龙2 小时前
框架模块说明 #05 权限管理_03
java·开发语言·spring
开心羊咩咩3 小时前
Idea 2024.3 突然出现点击run 运行没有反应,且没有任何提示。
java·ide·intellij-idea
waterme1onY3 小时前
IDEA中MAVEN的一些设置问题
java·maven·intellij-idea
阿华的代码王国3 小时前
【算法】——前缀和(矩阵区域和详解,文末附)
java·开发语言·算法·前缀和
梦.清..3 小时前
面向对象(二)——类和对象(上)
java