当前位置: 首页 > news >正文

Mybatis执行SQL流程(六)之Executor执行器

文章目录

      • 执行器
        • BaseExecutor
        • SimpleExecutor
        • ReuseExecutor
        • BatchExecutor
        • 如何指定执行器类型
      • 源码
        • insert操作
        • select

执行器

BaseExecutor

执行器的抽象基类

1、模板方法模式

定义了执行器的通用骨架(如查询/更新流程、事务提交/回滚、本地缓存管理),将具体操作延迟到子类实现(SimpleExecutor、ReuseExecutor、BatchExecutor)

2、一级缓存(Local Cache)管理

默认开启,缓存范围限定在SqlSession 生命周期内(即会话级缓存),通过PerpetualCache实现

SimpleExecutor
  • 工作原理
    • 每次执行 select 或 update 操作时,都会创建一个新的 PreparedStatement 对象。
    • 执行完毕后,立即关闭这个Statement 对象(以及关联的游标ResultSet)
  • 特点
    • 简单直接:逻辑清晰,没有复杂的缓存或复用机制
    • 资源开销大:频繁创建和销毁Statement 对象会带来额外的开销,尤其是在循环中多次执行SQL时。
    • 无重用:相同的SQL语句每次都会创建新的PreparedStatement。
  • 适用场景:对性能要求不高、SQL执行不频繁或每次SQL都不同的简单应用。作为默认选项,它最不容易出错。
ReuseExecutor
  • 工作原理
    • 在同一个SqlSession 的生命周期内,会将执行过的PreparedStatement 缓存起来(以SQL语句本身作为Key)
    • 当再次执行完全相同的SQL 语句(字符串内容相同)时,会从缓存中取出并复用这个PreparedStatement。
    • 不会在执行后立即关闭Statement,而是在SqlSession 关闭时统一关闭所有缓存的Statement。
  • 特点
    • 减少创建开销:避免了重复编译相同的SQL 语句,提高了执行相同 SQL 的效率
    • 节省资源:复用PreparedStatement 减少了数据库驱动创建对象的开销
    • 潜在内存泄漏风险:如果 SqlSession 生命周期很长且执行了大量不同的SQL,缓存会持续增长,占用内存。
    • 依赖 SQL 字符串匹配:必须SQL字符串(包括空格、换行)完全一致才能复用
  • 适用场景:在同一个 SqlSession 中需要反复执行完全相同SQL 语句的场景(例如循环插入大量数据,但每条SQL 都相同)。相比 BatchExecutor 灵活性稍高
BatchExecutor
  • 工作原理
    • 针对update 和 insert 语句(select会退化为SimpleExecutor行为)
    • 不会立即执行每一条SQL。而是将多个update/insert 语句添加到一个批处理队列中
    • 当调用 commit(), flushStatement() 或执行一个 select 语句(会触发自动flush)时,一次性将队列中的所有语句发送到数据库执行(使用jdbc 的 addBatch() 和 executeBatch())
    • 对于select语句,他会立即执行,因为它无法批量处理查询
  • 特点
    • 大幅提升批量操作性能:将多次网络往返减少为1次(或少量几次),这是最主要的优势
    • 必须显示提交/刷新:开发者需要理解并正确使用 commit 或 flushStatements 来确保 SQL 真正执行到数据库。忘记提交会导致操作丢失。
    • 事务边界:通常与显示事务管理结合使用,批量操作要么全部重构,要么全部失败(取决于数据库和事务设置)
    • 内存管理:大量未提交的批处理操作会占用JDBC驱动和数据库的资源。
  • 适用场景:需要高效执行大量 insert 或 update 操作的场景。
如何指定执行器类型

方法一:

在创建SqlSession时,通过 SqlSessionFactory.openSession() 方法的重载版本指定 ExecutorType 参数:

// 使用默认的 SimpleExecutor (等同于 openSession())
SqlSession session = sqlSessionFactory.openSession();// 显式指定为 ReuseExecutor
SqlSession session = sqlSessionFactory.openSession(ExecutorType.REUSE);// 显式指定为 BatchExecutor (通常需要同时指定事务控制)
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
// 对于 BatchExecutor, 通常需要手动提交
try {YourMapper mapper = session.getMapper(YourMapper.class);for (YourObject obj : list) {mapper.insert(obj); // 此时只是加入批处理队列,未执行}session.commit(); // 或 session.flushStatements(); 真正执行批处理
} finally {session.close();
}

实践:

    @ResourceSqlSessionFactory sqlSessionFactory;@GetMapping("/{id}")public User getUserById(@PathVariable Long id) {SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserById(id);sqlSession.close();return user;}

方法二:

在mybatis-config.xml文件中配置全局执行类型

<configuration><settings><!-- 设置默认执行器类型 --><setting name="defaultExecutorType" value="BATCH"/> </settings>
</configuration>

或者application.yml中配置:

mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.zy.entityconfiguration:map-underscore-to-camel-case: truecache-enabled: truedefault-executor-type: batch # 设置执行器类型

方法三:接入springboot项目

// 可以配置多个模板
@Bean(name = "batchSqlSessionTemplate")
public SqlSessionTemplate batchSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
}@Bean(name = "reuseSqlSessionTemplate")
public SqlSessionTemplate reuseSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.REUSE);
}// 使用时注入
@Service
public class UserService {@Autowired @Qualifier("batchSqlSessionTemplate") // 注入批量模板private SqlSessionTemplate batchSqlSessionTemplate;public void batchInsert(List<User> users) {UserMapper mapper = batchSqlSessionTemplate.getMapper(UserMapper.class);for (User user : users) {mapper.insert(user);}batchSqlSessionTemplate.commit(); // 提交批处理}
}

不做配置时默认为Simple。

源码

从上一篇文里面了解到:MapperMethod 中执行对方法进行了区分,Sqlsession调用 Executor 对应的方法执行sql。以SimpleExecutor的源码了解:

insert操作
// BaseExecutor.java
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}clearLocalCache(); // 清空一级缓存return doUpdate(ms, parameter); // 子类重写
}
// SimpleExecutor 重写的方法,例如执行 sql:@Insert("insert into user(id, username) values (#{id}, #{username})")
// 大致流程如下:
doUpdate-> prepareStatement-> getConnection-> handler.prepare // 通过connection 实例化一个Statement对象-> handler.paramterize // 通过typeHandler为PreparedStatement对象设置数据,即如使用jdbc那样为PreparedStatement更新对应占位符对应的数据-> handler.update-> PreparedStatement.execute() // 执行sql-> keyGenerator.processAfter // 前面设置的KeyGenerator processAfter方法此处生效// SimpleExecutor.java:
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); // 此处boundSql传参为null,在初始化StatementHandler中,会根据KeyGenerator类型来实例化boundSql,详细内容见KeyGeneratorstmt = prepareStatement(handler, ms.getStatementLog());  // 实例化PrepareStatement对象,并且对占位符设置参数return handler.update(stmt);} finally {closeStatement(stmt);}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog); // 项目中与Springboot进行结合,配置了DataSource,若未指定哪一种数据库连接池管理的话,则默认使用Hikaristmt = handler.prepare(connection, transaction.getTimeout()); // 使用StatementHandler实例化Statement对象handler.parameterize(stmt);return stmt;
}
// BaseStatementHandler 的 prepare 方法
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {statement = instantiateStatement(connection); // 源码见下文setStatementTimeout(statement, transactionTimeout);setFetchSize(statement);return statement;} catch (SQLException e) {closeStatement(statement);throw e;} catch (Exception e) {closeStatement(statement);throw new ExecutorException("Error preparing statement.  Cause: " + e, e);}
}
// 以 PreparedStatementHandler 为例
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {String[] keyColumnNames = mappedStatement.getKeyColumns();if (keyColumnNames == null) {return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);} else {return connection.prepareStatement(sql, keyColumnNames);}} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {// 涉及到ResultSet类型,后续补充。当执行前面的insert语句时,进入该分支return connection.prepareStatement(sql); // hikari还为连接做了一层代理。在项目中配置的Mysql数据库,即本质上还是Mysql的connection实现类执行方法} else {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);}
}
// 以 PreparedStatementHandler 为例:对Statement中的占位符进行处理
@Override
public void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);
}
// DefaultParameterHandler 中的 方法
@Override
public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {// 存在参数类型对应的TypeHandlervalue = parameterObject;} else {// 此处逻辑较为复杂,MetaObject后续在KeyGenerator中也使用到,此处简单说明,后续详细说明。/** 执行的接口为:@Insert("insert into user(id, username) values (#{id}, #{username})")int insertUser(User user);入参为自定义Bean,即parameterObject类型为User类型。要获取User对象的id、username属性值,通过反射调用User的getId(),getUsername()方法来获取对应的属性值。*/MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}TypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {typeHandler.setParameter(ps, i + 1, value, jdbcType); // 使用 TypeHandler 为PreparedStatement中的占位符替换数据。TypeHandler 的详细内容见 TypeHandler 环节} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}
}

预处理完成后,接着就是执行sql

// 以 PreparedStatementHandler 为例
@Override
public int update(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute(); // 执行sqlint rows = ps.getUpdateCount();Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject); // 执行KeyGenerator的processAfter方法。上述sql中不需要执行KeyGeneratorreturn rows;
}
select
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);// 涉及到二级缓存查询。缓存查询流程见二级缓存环节return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// 此处越过二级缓存,进入数据库查询流程
// SimpleExecutor.java@Overridepublic <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);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler); // 使用StatementHandler执行查询。由子类重写该方法} finally {closeStatement(stmt);}}
// 以PreparedStatementHandler为例
@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute(); // 执行sqlreturn resultSetHandler.handleResultSets(ps);// 处理查询的结果集。内容还有点多,后续再单独补充吧}

总结:
对于查询来说,主要涉及到二级缓存、一级缓存、参数处理、结果集处理。
对于新增来说,涉及到主键自动生成keyGenerator、一级缓存清除、参数处理、typeHandler。

其中参数处理时,使用 MetaObject metaObject获取对应属性值;结果集处理时使用 MetaObject metaObject设置属性值。参数处理时都包含了对TypeHandler的使用。

KeyGenerator包含sql执行前获取主键、执行后获取主键。

真正执行sql是交给jdbc执行的。

http://www.dtcms.com/a/342704.html

相关文章:

  • ubuntu配置Zotero+翻译插件+坚果云同步
  • 30、ICS/OT 攻击溯源 (电力系统) 模拟组件 - /安全与维护组件/ics-attack-forensics
  • 学习 k 均值聚类算法的心得
  • 记录一次el-table+sortablejs的拖拽bug
  • UTF-8 编码
  • 基于IPO智能粒子优化的IIR滤波器参数识别算法matlab仿真
  • 250821-RHEL9.4上Docker及Docker-Compose的离线安装
  • 大数据仓库分层
  • windows 下控制台只能输入或输入输出的问题
  • Java -- 互斥锁--死锁--释放锁
  • 机器学习两大核心算法:集成学习与 K-Means 聚类详解
  • 机器学习经典算法总结:K-Means聚类与集成学习(Bagging, Boosting, Stacking)
  • 机器学习核心算法笔记:集成学习与聚类算法
  • QT6(QSpinBox和QDoubleSpinBox)
  • java项目数据脱敏工具类实现
  • 【离线安装】CentOS Linux 7 上离线部署Oracle 19c(已成功安装2次)
  • 【数据可视化-96】使用 Pyecharts 绘制主题河流图(ThemeRiver):步骤与数据组织形式
  • 如何使用 DeepSeek 助力工作​
  • C# 13 与 .NET 9 跨平台开发实战(第一章:开发环境搭建与.NET概述-下篇)
  • 阿里云的centos8 服务器安装MySQL 8.0
  • 【LeetCode 415】—字符串相加算法详解
  • Java学习历程14——制作一款五子棋游戏(4)
  • R 语言科研配色 --- 第 85 期 (附免费下载的配色绘图PPT)
  • 全屋WiFi强电款WiFi6 86面板一站式测试解决方案
  • leetcode 904 水果成篮
  • 从零开始理解 K 均值聚类:原理、实现与应用
  • Grafana侧重可视化,那多数据源告警呢?
  • Linux的奇妙冒险——进程间通信(管道、SystemV IPC)
  • 【实战记录】麒麟服务器操作系统安装KSC-Defender安全中心全指南
  • EagleTrader交易员采访|交易是一场概率游戏