Mybatis执行器

news/2024/10/5 6:07:04/文章来源:https://www.cnblogs.com/xw-01/p/18272133

mybatis执行sql语句的操作是由执行器(Executor)完成的,mybatis中一共提供了3种Executor

类型 名称 功能
REUSE 重用执行器 缓存PreparedStatement,下一次执行相同的sql可重用
BATCH 批量执行器 将修改操作记录在本地,等待程序触发或有下一次查询时才批量执行修改操作
SIMPLE 简单执行器 对每一次执行都生成PreparedStatement,执行完就关闭,不缓存

另外,mybatis 还提供了一个缓存执行器CachingExecutor,该执行器实际上是以上三种执行器的装饰类,用以处理缓存相关操作,实际干活的还是以上三种执行器之一。

Executor的继续结构如下:

image

1. BaseExecutor

BaseExecutor实现了Executor的基本操作,如:

  • 事务的处理:

    • commit(...):处理事务的提交
    • rollback(...):处理事务的回滚
  • 缓存的处理:

    • createCacheKey(...):创建缓存key
    • clearLocalCache(...):清除缓存
  • curd操作:

    • query(...):查询操作
    • update(...):更新操作,插入与删除也是在这里处理
  • 留待子类的实现

    • doUpdate(...):具体的更新操作,留待子类实现
    • doQuery(...):具体的查询操作,留待子类实现

接下来我们关注Executor的实现时,只关注留待子类实现的方法。

2. SimpleExecutor

SimpleExecutor会对每一次执行都生成PreparedStatement,执行完就关闭,不缓存,我们来看看它是怎么实现的,来看看它的doQuery(...)方法:

  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();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 得到 PrepareStatementstmt = prepareStatement(handler, ms.getStatementLog());// 执行查询return handler.query(stmt, resultHandler);} finally {// 关闭 StatementcloseStatement(stmt);}}

获取Statement的方法为SimpleExecutor#prepareStatement

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 获取数据库连接Connection connection = getConnection(statementLog);// 获取 Statementstmt = handler.prepare(connection, transaction.getTimeout());// 处理参数设置handler.parameterize(stmt);return stmt;}

这个方法先是获取了数据库连接,接着获取Statement,然后处理了参数设置。

关于数据库连接的获取,我们在分析配置文件的解析时,数据源的配置最终会转化成PooledDataSourceUnpooledDataSource对象,数据库连接就是从数据源来的。

至于Statement的生成,PreparedStatement的实例化操作方法为PreparedStatementHandler#instantiateStatement,这些都是常规的jdbc操作,就不细看了。

处理sql的执行方法为PreparedStatementHandler#query

  @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;// 执行ps.execute();return resultSetHandler.handleResultSets(ps);}

SimpleExecutor#doQuery(...)的执行流程如下:

  • 获取数据库连接
  • 获取PrepareStatement
  • 执行查询
  • 关闭PrepareStatement

SimpleExecutor的操作就是常规的jdbc操作。

3. ReuseExecutor

ReuseExecutor会缓存PreparedStatement,下一次执行相同的sql可重用。

我们依然分析doQuery(...)方法:

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 获取 StatementStatement stmt = prepareStatement(handler, ms.getStatementLog());// 处理查询操作return handler.query(stmt, resultHandler);}

SimpleExecutor相比,ReuseExecutordoQuery(...)方法并没关闭Statement.我们来看看Statement的获取操作:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;BoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();// 根据sql语句判断是否有Statement缓存if (hasStatementFor(sql)) {// 有缓存,直接使用stmt = getStatement(sql);applyTransactionTimeout(stmt);} else {// 没缓存,获取数据库连接,再获取 StatementConnection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());// 缓存 StatementputStatement(sql, stmt);}// 处理参数handler.parameterize(stmt);return stmt;
}

可以看到,ReuseExecutor获取Statement时,会先从缓存里获取,缓存里没有才会新建一个Statement,然后将新建的Statement添加到缓存中。从这里可以看出,ReuseExecutorReuse,复用的是Statement

我们再来看看缓存Statement的结构:

public class ReuseExecutor extends BaseExecutor {private final Map<String, Statement> statementMap = new HashMap<>();...private Statement getStatement(String s) {return statementMap.get(s);}private void putStatement(String sql, Statement stmt) {statementMap.put(sql, stmt);}
}

由些可见,缓存Statement的是一个Mapkeysql语句,valueStatement.

4. BatchExecutor

BatchExecutor会将修改操作记录在本地,等待程序触发或有下一次查询时才批量执行修改操作,即:

  • 进行修改操作(insertupdatedelete)时,并不会立即执行,而是会缓存到本地
  • 进行查询操作(select)时,会先处理缓存到本地的修改操作,再进行查询操作
  • 也可行触发修改操作

从以上内容来看,这种方式似乎有大坑,列举几点如下:

  • 修改操作缓存到本地后,如果执行前遇到意外重启,缓存的记录会不会丢失?
  • 分布式环境下,多机共同协作,更新在A机上执行,查询在B机上执行,B机是不是不能查到B机的更新记录(B机的更新操作还在缓存中,并未执行)?

我们来看下BatchExecutor的更新操作,进入doUpdate(...)方法:

  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {final Configuration configuration = ms.getConfiguration();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;// 如果传入的sql是当前保存的 sql,直接使用if (sql.equals(currentSql) && ms.equals(currentStatement)) {int last = statementList.size() - 1;stmt = statementList.get(last);applyTransactionTimeout(stmt);handler.parameterize(stmt);// fix Issues 322BatchResult batchResult = batchResultList.get(last);batchResult.addParameterObject(parameterObject);} else {// 创建连接,获取 StatementConnection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);    // fix Issues 322currentSql = sql;currentStatement = ms;statementList.add(stmt);batchResultList.add(new BatchResult(ms, sql, parameterObject));}// 保存,等待之后批量执行handler.batch(stmt);return BATCH_UPDATE_RETURN_VALUE;}

BatchExecutor有成员变量会记录上一次执行的sqlMappedStatement,如果本次执行的sqlMappedStatement与上一次执行的相同,则直接使用上一次的Statement,否则就新建连接、获取Statement.

得到Statement后,会调用PreparedStatementHandler#batch方法:

  public void batch(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.addBatch();}

这个方法并没有执行,只是调用PreparedStatement#addBatch方法,将当前statement保存了起来。

PreparedStatement#addBatch方法如何使用呢?简单示意下:

// 获取连接
Connection connection = getConnection();
// 预编译sql
String sql = "xxx";
PreparedStatement statement = connection.prepareStatement(sql);   
//记录1
statement.setInt(1, 1);
statement.setString(2, "one");
statement.addBatch();   
//记录2
statement.setInt(1, 2);
statement.setString(2, "two");
statement.addBatch();   
//记录3
statement.setInt(1, 3);
statement.setString(2, "three");
statement.addBatch();   
//批量执行
int[] counts = statement.executeBatch();
// 关闭statment,关闭连接
...

BatchExecutordoUpdate(...)方法并没有执行sql语句,我们再来看看doQuery(...)方法:

  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {// 处理缓存中的 statementsflushStatements();Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);// 获取连接,获取Statement,处理参数Connection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);// 执行查询return handler.query(stmt, resultHandler);} finally {// 关闭 StatementcloseStatement(stmt);}}

doQuery(...)方法会先调用flushStatements()方法,然后再处理查询操作,整个过程基本同SimpleExecutor一致,即”获取数据库连接-获取Statement-处理查询-关闭Statement“等几步。我们重点来看flushStatements()方法的流程.

flushStatements()方法最终调用的是BatchExecutor#doFlushStatements方法,代码如下:

  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {try {List<BatchResult> results = new ArrayList<>();if (isRollback) {return Collections.emptyList();}// 遍历的statementList,statementList就是缓存statement的结构for (int i = 0, n = statementList.size(); i < n; i++) {Statement stmt = statementList.get(i);applyTransactionTimeout(stmt);BatchResult batchResult = batchResultList.get(i);try {// 关键代码:stmt.executeBatch(),批量执行sqlbatchResult.setUpdateCounts(stmt.executeBatch());...} catch (BatchUpdateException e) {...}results.add(batchResult);}return results;} finally {...}}

BatchExecutor#doFlushStatements方法的关键代码就是batchResult.setUpdateCounts(stmt.executeBatch());了 ,其中的stmt.executeBatch()就是批量执行更新操作了。

从以上分析可知,BatchExecutor#doUpdate(...)方法不会执行sql语句,只是把sql语句转换为Statement然后缓存起来,在执行BatchExecutor#doQuery(...)方法时,会先执行缓存起来的Statement,然后再执行查询操作,当然也可以手动调用BatchExecutor#flushStatements方法执行缓存的Statement

5. CachingExecutor

CachingExecutor不同于以上3种执行器,它是一个装饰类,可以从缓存中获取数据,实际干活的还是以上三种执行器之一:

public class CachingExecutor implements Executor {// 具体的执行器private final Executor delegate;private final TransactionalCacheManager tcm = new TransactionalCacheManager();public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}...
}

从代码来看,它是Executor的子类,其中有一个成员变量delegate,它的类型为Executor,由构造方法传入。也就是说,在创建CachingExecutor时,会传入以上3种执行器之一,CachingExecutor会把它保存到成员变量delegate中。

CachingExecutor的query(...)方法如下:

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);// 创建缓存keyCacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {Cache cache = ms.getCache();// 操作缓存if (cache != null) {flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler ** null) {ensureNoOutParams(ms, boundSql);@SuppressWarnings("unchecked")// 从缓存中获取  List<E> list = (List<E>) tcm.getObject(cache, key);if (list ** null) {// 实际处理查询的操作  list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 添加到缓存中tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

从代码上来看,CachingExecutor在处理查询时,会先从缓存中获取,当缓存中不存在时,就执行具体执行器的query(xxx)方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/737004.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

本地安装seata

1. 下载,解压steata安装包 2. 修改配置里面的端口号 只要是localhost或者不是自己的端口号都更改成自己的端口号3. 修改配置nacos的文件执行 4、数据库中添加对应的表 数据库中的表名称尽量位seata_server,避免后面去修改 5、线上nacos配置 在public 下面进行配置,修改自己对…

关于自定义unordered_set\unordered_map中Hash和KeyEqual:函数对象和lambda表达式简单应用

以unordered_set为例,首先在cppreference中查看其模板定义:可以看到Hash类默认是std::hash<Key,KeyEqual类似,本文将Hash以函数对象写出,将KeyEqual以lambda写出。 class hashvec{public:size_t operator()(const vector<int> & vec) const {return hash<…

Python预测体重变化:决策树、tf神经网络、随机森林、梯度提升树、线性回归可视化分析吸烟与健康调查数据

全文链接:https://tecdat.cn/?p=36648 原文出处:拓端数据部落公众号 在当今的数据驱动时代,机器学习算法已成为解析复杂数据集、揭示隐藏模式及预测未来趋势的重要工具。特别是在医疗健康领域,这些算法的应用极大地提升了我们对疾病预防、诊断及治疗方案的理解与制定能力。…

Nuxt3 的生命周期和钩子函数(九)

摘要:本文介绍了Nuxt3中与Vite相关的五个生命周期钩子,包括vite:extend、vite:extendConfig、vite:configResolved、vite:serverCreated和vite:compiled,展示了如何在每个钩子中扩展Vite配置、读取配置、添加中间件和处理编译事件。每个钩子都有详细的描述和示例代码,帮助开…

地理信息科学:生态保护的智慧经纬

在地球这颗蓝色星球上,每一片森林的呼吸、每一条河流的流淌,都是生命交响曲中不可或缺的音符。而地理信息科学(GIS),正是我们手中解读自然密码、护航生态平衡的精密仪器。今天,让我们深入探讨GIS如何在生物多样性保护和生态系统管理中发挥其不可替代的作用。 🌱 GIS——…

软连接与硬链接

(1)软链接(symbolic link) 创建命令:ln -s <target> <link_name>其中:<target> 是目标文件或目录的路径,可以是相对路径或绝对路径。<link_name> 是要创建的软链接的名称,可以是相对路径或绝对路径。特点:笔试面试填空题和简答题:软链接的特点…

pycharm导入第三方包出现红色波浪线或新建flask项目出现红色波浪线解决办法

设置 -> 项目结构(Project Structure) ,将site-packages设为源代码

开源软件开发平台哪家好?

低代码技术平台、开源软件开发平台哪家好?进行数字化转型,离不开低代码技术平台等软件产品的加持与助力。因为它更好操作、更灵活、易维护等优势特点突出,在推动企业实现流程化办公的过程中助力明显,作用大,深得客户喜爱。那么,低代码技术平台、开源软件开发平台哪家好?…

阿里云 SAE 助力修正商城 3 周内提升系统承载能力 20 倍,轻松应对春晚流量

修正技术团队迫切需要升级 APP 架构以应对即将到来的超高并发场景。这一挑战不仅是对技术的考验,更是对修正品牌实力的一次展示。为了应对这次巨大的技术挑战,修正技术团队选择与阿里云云原生团队合作,进行 APP 架构的升级。作者:赵世振、刘松伟、朱坪" 从了解阿里云 …

使用yum 命令安装 dotnet 6

如果是超级管理员账号root 登录 不需要加 sudo。 sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpmsudo yum install dotnet-sdk-6.0sudo yum install dotnet-runtime-6.0 输入dotnet --info 查看 安装的 dotnet 版本

SpringCloud Alibaba Nacos 配置动态更新源码学习总结

众所周知,nacos两大核心功能,服务注册发现与动态配置 支持服务注册发现的有:Eureka、Consul、Zookeeper、Nacos 支持动态配置的有:Spring Cloud Config、Nacos、Apollo、Consul 像支持分布式的框架,必须得借用第三方服务,比如定时任务调度xxl-job,分布式事务seata,都分为…

实战篇——SQL注入sqli-labs-master靶场实战三

实战篇——SQL注入sqli-labs-master靶场实战(3) sqlmap基础用法 GET型:POST型:请求头注入(以User-Agent注入为例):爆库: python sqlmap.py -r C:\Users\yaogu\Desktop\sqlmap\request\1.txt --threads 10 --dbs爆表: python sqlmap.py -r C:\Users\yaogu\Desktop\sqlmap\r…