侧边栏壁纸
博主头像
极简笔记博主等级

极简笔记,书写你的精彩程序人生!

  • 累计撰写 147 篇文章
  • 累计创建 24 个标签
  • 累计收到 8 条评论

目 录CONTENT

文章目录

MyBatis核心源码深度剖析②

极简笔记
2022-07-05 / 0 评论 / 0 点赞 / 864 阅读 / 3,714 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-07-05,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

核心执行器executor详解

4.1 jdbc回顾

1) 回顾JDBC执行过程

添加jar包 -> 获得连接 -> 预编译SQL -> 执行SQL,读取结果 -> 关闭事务

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/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT", "root", "root");
    // 3、编写sql,进行预编译
    String sql = " select * from tb_brand;";
    PreparedStatement ps = con.prepareStatement(sql);
    // 4、执行查询,得到结果集
    ResultSet rs = ps.executeQuery();
    while (rs.next()) {
        int bid = rs.getInt("bid");
        String bname = rs.getString("bname");
        System.out.println("====> bid=" + bid + "\tbname=" + bname);
    }
    //5、关闭事务
    rs.close();
    ps.close();
    con.close();
}

2) MyBatis对JDBC封装的执行过程

image-1657003734190

4.2 MyBatis的核心执行组件介绍

在Mybatis中,SqlSession对数据库的操作,将委托给执行器Executor来完成都将委托给执行器Executor来完成;
Mybatis执行过程,主要的执行模块是: SqlSession->Executor->StatementHandler->数据库

四个核心组件:

① 动态代理 MapperProxy
② SQL会话 SqlSession
③ 执行器 Executor
④ JDBC处理器 StatementHandler

1)SqlSession

SqlSession采用了门面模式方便来让我们使用。他不能跨线程使用,一级缓存生命周期和它一致;
基本功能:增删改查基本的操作功能;
辅助功能:提交、关闭会话;
门面模式:提供一个统一的门面接口API,使得系统更加容易使用。

2)Executor

基本功能:改、查、维护缓存
辅助功能:提交、关闭执行器、批处理刷新
Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给 StatementHandler完成。

3)StatementHandler

经过执行器处理后交给了StatementHandler(声明处理器);
主要作用就是:参数处理、结果处理;
StatementHandler 首先通过 ParameterHandler 完成 SQL 语句的实参绑定,然后通过 java.sql.Statement 对象执行 SQL 语句并得到结果集,最后通过 ResultSetHandler 完成结果集的映射,得到结果对象并返回。

4.3 Executor执行器分析

1) JDBC中的执行器

JDBC有三种执行器分别是Statement(简单执行器)、PreparedStatement(预处理执行器)、CallableStatement(存储过程执行器)

Statement:基本功能:执行静态SQL

PreparedStatement:设置预编译,防止SQL注入

CallableStatement:设置出参、读取参数(用于执行存储过程)

2) Mybatis执行器

Executor继承结构分析

image-1657003720056

Mybatis给我们提供了三种执行器,分别是 :

  • SimpleExecutor(简单执行器)、
  • ResuseExecutor(可重用执行器)、
  • BathExecutor(批处理执行器)

这三个执行器继承了一个BaseExecutor(基础执行器),而这个基础执行器实现了Executor接口,其中简单执行器是默认的执行器。

其实还有一种执行器CachingExecutor(二级缓存执行器)你开启二级缓存则会实例化它,在 BaseExecutor 的基础上,实现二级缓存功能。 (注意: BaseExecutor 的本地缓存,就是一级缓存。)

(1)Executor接口

org.apache.ibatis.executor.Executor ,执行器接口。

主要定义了以下内容:

读和写操作相关的方法
事务相关的方法
缓存相关的方法
设置延迟加载的方法
设置包装的 Executor 对象的方法

public interface Executor {

    // 空 ResultHandler 对象的枚举
    ResultHandler NO_RESULT_HANDLER = null;

    // 更新 or 插入 or 删除,由传入的 MappedStatement 的 SQL 所决定
    int update(MappedStatement ms, Object parameter) throws SQLException;

    // 查询,带 ResultHandler + CacheKey + BoundSql
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    // 查询,带 ResultHandler
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    // 查询,返回值为 Cursor
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

    // 刷入批处理语句
    List<BatchResult> flushStatements() throws SQLException;

    // 提交事务
    void commit(boolean required) throws SQLException;
    // 回滚事务
    void rollback(boolean required) throws SQLException;

    // 创建 CacheKey 对象
    CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
    // 判断是否缓存
    boolean isCached(MappedStatement ms, CacheKey key);
    // 清除本地缓存
    void clearLocalCache();

    // 延迟加载
    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

    // 获得事务
    Transaction getTransaction();
    // 关闭事务
    void close(boolean forceRollback);
    // 判断事务是否关闭
    boolean isClosed();

    // 设置包装的 Executor 对象
    void setExecutorWrapper(Executor executor);

}

(2)BaseExecutor(基础执行器)

基础执行器:维护一级缓存,是simple、reuse、batch这三个执行器的父类。主要逻辑是维护缓存,其他实现交给子类。org.apache.ibatis.executor.BaseExecutor 实现 Executor 接口,提供骨架方法,从而使子类只要实现指定的几个抽象方法即可。

/**
 * 事务对象
 */
protected Transaction transaction;
/**
 * 包装的 Executor 对象
 */
protected Executor wrapper;

/**
 * DeferredLoad( 延迟加载 ) 队列
 */
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
/**
 * 本地缓存,即一级缓存
 */
protected PerpetualCache localCache;
/**
 * 本地输出类型的参数的缓存
 */
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;

/**
 * 记录嵌套查询的层级
 */
protected int queryStack;
/**
 * 是否关闭
 */
private boolean closed;

// ************************

/**
	clearLocalCache() 方法,清理一级(本地)缓存
*/
@Override
public void clearLocalCache() {
    if (!closed) {
        // 清理 localCache
        localCache.clear();
        // 清理 localOutputParameterCache
        localOutputParameterCache.clear();
    }
}


// createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) 方法,创建 CacheKey 对象



// isCached(MappedStatement ms, CacheKey key) 方法,判断一级缓存是否存在


// query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // <1> 获得 BoundSql 对象
    BoundSql boundSql = ms.getBoundSql(parameter);
    // <2> 创建 CacheKey 对象
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // <3> 查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}


// update方法
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    // <1> 已经关闭,则抛出 ExecutorException 异常
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    // <2> 清空本地缓存
    clearLocalCache();
    // <3> 执行写操作
    return doUpdate(ms, parameter);
}

(3)SimpleExecutor(简单执行器)

每次读或写操作都会创建一个新的预处理器(PrepareStatement);
每次执行的的SQL都会进行一次预编译;
执行完成后,关闭该 Statement 对象。

org.apache.ibatis.executor.SimpleExecutor ,继承 BaseExecutor 抽象类,简单的 Executor 实现类。

 // 查询

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        // <1> 创建 StatementHandler 对象
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // <2> 初始化 StatementHandler 对象
        stmt = prepareStatement(handler, ms.getStatementLog());
        // <3> 执行 StatementHandler  ,进行读操作
        return handler.query(stmt, resultHandler);
    } finally {
        // <4> 关闭 StatementHandler 对象
        closeStatement(stmt);
    }
}



private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // <2.1> 获得 Connection 对象
    Connection connection = getConnection(statementLog);
    // <2.2> 创建 Statement 或 PrepareStatement 对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // <2.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
    handler.parameterize(stmt);
    return stmt;
}



// 更新
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        // 创建 StatementHandler 对象
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
        // 初始化 StatementHandler 对象
        stmt = prepareStatement(handler, ms.getStatementLog());
        // <3> 执行 StatementHandler ,进行写操作
        return handler.update(stmt);
    } finally {
        // 关闭 StatementHandler 对象
        closeStatement(stmt);
    }
}

(4)ReuseExecutor(可重用执行器)

每次开始读或写操作,以sql作为key,查找Statement对象优先从缓存中获取对应的 Statement 对象。如果不存在,才进行创建。( 只要是相同的SQL只会进行一次预处理)
执行完成后,不关闭该 Statement 对象,而是放置于Map<String, Statement>内,供下一次使用。
其它的,和 SimpleExecutor 是一致的。

原理:

//可重用的执行器内部用了一个map,用来缓存SQL语句对应的Statement对象
//Key是我们的SQL语句,value是我们的Statement对象
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();

org.apache.ibatis.executor.ReuseExecutor ,继承 BaseExecutor 抽象类,可重用的 Executor 实现类。

// doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    // 创建 StatementHandler 对象
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // 初始化 StatementHandler 对象
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    // 执行 StatementHandler  ,进行读操作
    return handler.query(stmt, resultHandler);
}

// doUpdate
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    // 创建 StatementHandler 对象
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    // 初始化 StatementHandler 对象
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    // 执行 StatementHandler  ,进行写操作
    return handler.update(stmt);
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    // 存在
    if (hasStatementFor(sql)) {
        // <1.1> 从缓存中获得 Statement 或 PrepareStatement 对象
        stmt = getStatement(sql);
        // <1.2> 设置事务超时时间
        applyTransactionTimeout(stmt);
    // 不存在
    } else {
        // <2.1> 获得 Connection 对象
        Connection connection = getConnection(statementLog);
        // <2.2> 创建 Statement 或 PrepareStatement 对象
        stmt = handler.prepare(connection, transaction.getTimeout());
        // <2.3> 添加到缓存中
        putStatement(sql, stmt);
    }
    // <2> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
    handler.parameterize(stmt);
    return stmt;
}

// 判断是否存在对应的 Statement 对象
private boolean hasStatementFor(String sql) {
    try {
        return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
    } catch (SQLException e) {
        return false;
    }
}

注意区别

image-1657003653150

注意:

  • ReuseExecutor 考虑到重用性,但是 Statement 最终还是需要有地方关闭。答案就在 #doFlushStatements(boolean isRollback) 方法中。而 BaseExecutor 在关闭 #close() 方法中,最终也会调用该方法,从而完成关闭缓存的 Statement 对象们
  • BaseExecutor 在提交或者回滚事务方法中,最终也会调用该方法,也能完成关闭缓存的 Statement 对象们。

(5)BatchExecutor(批处理执行器)

  1. 批处理只对增删改SQL有效(没有select,JDBC批处理不支持select);
  2. 将所有增删改sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;
  3. 执行sql需要满足三个条件才能使用同一个Statement(使用同一个Statement是为了压缩体积、减少SQL预处理)
    1.sql相同
    2.同一个MappedStatement(sql标签的配置都在这里面)
    3.执行的顺序必须是连续的

org.apache.ibatis.executor.BatchExecutor ,继承 BaseExecutor 抽象类,批量执行的 Executor 实现类。

public class BatchExecutor extends BaseExecutor {

    /**
     * Statement 数组     */
    private final List<Statement> statementList = new ArrayList<>();
    /**
     * BatchResult 数组     *     * 每一个 BatchResult 元素,对应一个 {@link #statementList} 的 Statement 元素     */
    private final List<BatchResult> batchResultList = new ArrayList<>();
    /**
     * 当前 SQL     */
    private String currentSql;
    /**
     * 当前 MappedStatement 对象     */
    private MappedStatement currentStatement;
    
    
    
    //doUpdate
    @Override
    public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
        final Configuration configuration = ms.getConfiguration();
        // <1> 创建 StatementHandler 对象
        final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
        final BoundSql boundSql = handler.getBoundSql();
        final String sql = boundSql.getSql();
        final Statement stmt;
        // <2> 如果匹配最后一次 currentSql 和 currentStatement ,则聚合到 BatchResult 中
        if (sql.equals(currentSql) && ms.equals(currentStatement)) {
            // <2.1> 获得最后一次的 Statement 对象
            int last = statementList.size() - 1;
            stmt = statementList.get(last);
            // <2.2> 设置事务超时时间
            applyTransactionTimeout(stmt);
            // <2.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
            handler.parameterize(stmt);
            // <2.4> 获得最后一次的 BatchResult 对象,并添加参数到其中
            BatchResult batchResult = batchResultList.get(last);
            batchResult.addParameterObject(parameterObject);
        // <3> 如果不匹配最后一次 currentSql 和 currentStatement ,则新建 BatchResult 对象
        } else {
            // <3.1> 获得 Connection
            Connection connection = getConnection(ms.getStatementLog());
            // <3.2> 创建 Statement 或 PrepareStatement 对象
            stmt = handler.prepare(connection, transaction.getTimeout());
            // <3.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
            handler.parameterize(stmt);    //fix Issues 322
            // <3.4> 重新设置 currentSql 和 currentStatement
            currentSql = sql;
            currentStatement = ms;
            // <3.5> 添加 Statement 到 statementList 中
            statementList.add(stmt);
            // <3.6> 创建 BatchResult 对象,并添加到 batchResultList 中
            batchResultList.add(new BatchResult(ms, sql, parameterObject));
        }
        // <4> 批处理
        handler.batch(stmt);
        return BATCH_UPDATE_RETURN_VALUE;
    }
    
    
    
    //doQuery
    //和 SimpleExecutor 的该方法,逻辑差不多。区别在发生查询之前,先调用 #flushStatements() 方法,刷入批处理语句。
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
            throws SQLException {
        Statement stmt = null;
        try {
            // *******************刷入批处理语句
            flushStatements();
            
            Configuration configuration = ms.getConfiguration();
            // 创建 StatementHandler 对象
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
            // 获得 Connection 对象
            Connection connection = getConnection(ms.getStatementLog());
            // 创建 Statement 或 PrepareStatement 对象
            stmt = handler.prepare(connection, transaction.getTimeout());
            // 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
            handler.parameterize(stmt);
            // 执行 StatementHandler  ,进行读操作
            return handler.query(stmt, resultHandler);
        } finally {
            // 关闭 StatementHandler 对象
            closeStatement(stmt);
        }
    }

}
  1. doUpdate代码上if (sql.equals(currentSql) && ms.equals(currentStatement))可以看出上一个添加的是否是这个sql(currentSql)并且是同一个MappedStatement currentStatement(映射语句);

  2. 满足条件就将参数放到当前这个BatchResult对象中的参数。属性是(parameterObject)【batchResult.addParameterObject(parameterObject);】

  3. 不满足条件则获取一个Statement实例 再实例化BatchResult对象。最后放到list中去,再给 current和MappedStatement currentStatement赋值

  4. 批处理提交必须执行flushStatements才会生效(会将一个个的Statement提交)可以减少与数据库交互次数

(6)CachingExecutor(二级缓存执行器)

CachingExecutor二级缓存执行器,属于缓存章节内容,在后面缓存章节详细讲解;

3) Executor的创建:

image-1657003604169

在上面的学习中,我们已经理解了各种 Executor 的实现代码。 那么,Executor 对象究竟在 MyBatis 中,是如何被创建的呢? 其实Configuration 类中,提供 newExecutor 方法,代码如下:

/**
 * 创建 Executor 对象  -----------------在创建SqlSession执行
 *
 * @param transaction 事务对象
 * @param executorType 执行器类型
 * @return Executor 对象
 */
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // <1> 获得执行器类型  
    //可以通过在 mybatis-config.xml 配置 <setting name="defaultExecutorType" value="" />
    executorType = executorType == null ? defaultExecutorType : executorType; // 使用默认
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 判断默认
    // <2> 3个分支3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor:  默认为 SimpleExecutor 对象
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }
    // <3> 如果开启缓存,创建 CachingExecutor 对象,装饰者模式进行包装  
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    // <4> 应用插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

小结:

我们可以在ExecutorType中看到,枚举了三种类型: SIMPLE 、REUSE 、BATCH
如果没有指定类型,默认为 SimpleExecutor 对象
如果开启缓存,创建 CachingExecutor 对象,进行包装


本文转自 博学谷-狂野架构,如有侵权,请联系删除。

0

评论区