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

MyBatis-Plus 批量操作 SQL 日志不打印问题解决方案

书接上文:SqlLogInterceptor mybatis配置打印SQL

问题描述

在使用 MyBatis-Plus 的saveBatch()updateBatchById()方法进行批量数据操作时,发现自定义的 Druid SQL 日志拦截器(SqlLogInterceptor)无法打印这些批量操作的 SQL 语句,导致调试和问题排查困难。

问题分析

1. 批量操作的特殊性

MyBatis-Plus 的批量操作方法(如saveBatchupdateBatchById)使用了 JDBC 的批处理(Batch)模式来提高性能。批处理模式与普通的单条 SQL 执行方式不同:

  • 普通执行:每条 SQL 单独执行,会触发statementExecuteAfterstatementExecuteUpdateAfter等方法
  • 批处理执行:多条 SQL 一起提交执行,只会触发statementExecuteBatchAfter方法

2. 原有拦截器的问题

原有的SqlLogInterceptor继承自 Druid 的FilterEventAdapter,实现了以下方法:

@Override protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) { statement.setLastExecuteTimeNano(); printSqlLog(statement, sql); } @Override protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) { statement.setLastExecuteTimeNano(); printSqlLog(statement, sql); } @Override protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) { statement.setLastExecuteTimeNano(); // 这里没有打印日志! }

可以看到,statementExecuteBatchAfter方法中只设置了执行时间,但没有打印 SQL 日志,这就是批量操作 SQL 不打印的根本原因。

解决方案

方案一:修改 Druid 拦截器(推荐)

修改SqlLogInterceptor.javastatementExecuteBatchAfter方法,添加 SQL 日志打印逻辑:

@Override protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) { statement.setLastExecuteTimeNano(); // 批量执行后也打印SQL日志 String sql = statement.getBatchSql(); if (StringUtil.isNotBlank(sql)) { printSqlLog(statement, sql); } else { // 如果批量SQL为空,尝试获取最后执行的SQL sql = statement.getLastExecuteSql(); if (StringUtil.isNotBlank(sql)) { printSqlLog(statement, sql); } } }

优点

  • 统一的日志格式
  • 包含执行时间统计
  • 可以格式化 SQL 和参数

缺点

  • 批量操作可能只显示一条 SQL 模板,看不到每条具体的参数

方案二:启用 MyBatis 原生日志

logback.xml配置文件中添加 MyBatis Mapper 的 DEBUG 级别日志:

<!-- MyBatis SQL 日志 --> <logger name="com.hzys.mapper" level="DEBUG"/>

优点

  • 可以看到每条 SQL 的详细参数
  • MyBatis 原生支持,稳定可靠

缺点

  • 日志格式与自定义拦截器不一致
  • 日志量较大

方案三:双管齐下(最佳实践)

同时使用方案一和方案二,既能保证批量操作的 SQL 被记录,又能在需要时查看详细的参数信息。

完整代码示例

1. 修改后的 SqlLogInterceptor

@Slf4j public class SqlLogInterceptor extends FilterEventAdapter { private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false); @Override protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) { statement.setLastExecuteTimeNano(); // 批量执行后也打印SQL日志 String sql = statement.getBatchSql(); if (StringUtil.isNotBlank(sql)) { printSqlLog(statement, sql); } else { // 如果批量SQL为空,尝试获取最后执行的SQL sql = statement.getLastExecuteSql(); if (StringUtil.isNotBlank(sql)) { printSqlLog(statement, sql); } } } private void printSqlLog(StatementProxy statement, String sql) { if (!log.isInfoEnabled() || StringUtil.isEmpty(sql)) { return; } try { // 获取参数 int parametersSize = statement.getParametersSize(); List<Object> parameters = new ArrayList<>(parametersSize); for (int i = 0; i < parametersSize; ++i) { parameters.add(getJdbcParameter(statement.getParameter(i))); } // 格式化SQL String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType(); String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION); // 打印日志 printSql(formattedSql, statement); } catch (Exception e) { log.error("SQL 格式化失败", e); log.info("\n\n============== Sql Start ==============\n" + "Execute SQL : {}\n" + "Execute Time: {}\n" + "============== Sql End ==============\n", sql, StringUtil.format(statement.getLastExecuteTimeNano())); } } private static void printSql(String sql, StatementProxy statement) { String sqlLogger = "\n\n============== Sql Start ==============" + "\nExecute SQL : {}" + "\nExecute Time: {}" + "\n============== Sql End ==============\n"; log.info(sqlLogger, sql.trim(), StringUtil.format(statement.getLastExecuteTimeNano())); } }

2. Logback 配置

<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="60 seconds"> <!-- 其他配置... --> <!-- 日志输出级别 --> <root level="INFO"> <appender-ref ref="STDOUT"/> <appender-ref ref="INFO"/> <appender-ref ref="WARN"/> <appender-ref ref="ERROR"/> </root> <!-- MyBatis SQL 日志 --> <logger name="com.hzys.mapper" level="DEBUG"/> <logger name="net.sf.ehcache" level="INFO"/> <logger name="druid.sql" level="INFO"/> </configuration>

3. Druid 配置

@Slf4j @Configuration public class DruidConfig { @Bean @Primary @ConfigurationProperties("spring.datasource.druid") public DataSource dataSource() { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); // 添加自定义的SQL日志拦截器 SqlLogInterceptor sqlLogInterceptor = new SqlLogInterceptor(); dataSource.getProxyFilters().add(sqlLogInterceptor); return dataSource; } }

验证效果

修改后,执行批量操作时会看到类似以下的日志输出:

============== Sql Start ============== Execute SQL : INSERT INTO employee_hourly_rate (id, project_member, member_level, ...) VALUES (?, ?, ?, ...) Execute Time: 15ms ============== Sql End ============== ==> Preparing: INSERT INTO employee_hourly_rate (id, project_member, member_level, ...) VALUES (?, ?, ?, ...) ==> Parameters: 1(Long), 张三(String), 高级工程师(String), ... ==> Parameters: 2(Long), 李四(String), 中级工程师(String), ... <== Updates: 2

注意事项

  1. 性能考虑:DEBUG 级别的 MyBatis 日志会输出大量信息,生产环境建议关闭或设置为 INFO 级别

  2. 日志过滤:可以在SqlLogInterceptor中添加过滤逻辑,避免打印某些不需要的 SQL(如健康检查)

  3. 批量大小:MyBatis-Plus 默认批量大小为 1000,可以通过配置调整

  4. 事务管理:批量操作需要在事务中执行,确保添加@Transactional注解

总结

MyBatis-Plus 批量操作 SQL 不打印的问题主要是因为批处理模式使用了不同的执行路径,原有的拦截器没有处理statementExecuteBatchAfter方法。通过修改拦截器并配合 MyBatis 原生日志,可以完美解决这个问题,既能保证日志的完整性,又能在需要时查看详细的执行信息。




http://www.jsqmd.com/news/436401/

相关文章:

  • 2026年口碑好的水环真空机组 厂家推荐:长吊引水真空机组值得信赖的生产厂家 - 行业平台推荐
  • 多模态大模型对齐实战教程(非常硬核),数据有限也能搞定,收藏这一篇就够了!
  • 2026年热门的EVA TAIC交联剂 品牌推荐:粉末TAIC交联剂/50粉末TAIC交联剂品牌厂家哪家靠谱 - 行业平台推荐
  • Node 快捷方式路径怎么获取
  • 用OpenClaw组建AI团队:一人顶一个部门的实战玩法
  • 重新安装指定 Node 版本、并切换了 Node 版本、但这里运行>npm -v 还是报错:‘npm‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。
  • CodeGenius Memory系统构建教程(非常详细),代码生成上下文控制从入门到精通,收藏这一篇就够了!
  • 【开题答辩全过程】以 衡水微法院小程序的设计与实现为例,包含答辩的问题和答案
  • 【机乎】Clawdbot之后,中文AI社交平台开启“祛魅”时刻
  • OpenClaw+RAG+Agent实战:打造能自动干活的数字员工
  • 2026 公认好用的 AI 论文软件,导师看了都夸专业
  • 阿里千问核心人员离职,AI战略何去何从?
  • 2026年热门的虾仁 工厂推荐:高邮大虾仁口碑好的厂家推荐 - 行业平台推荐
  • 【开题答辩全过程】以 高校学生社团管理系统为例,包含答辩的问题和答案
  • 智能体工程模式:编码新路径
  • 2026年评价高的悬链通过式抛丸机 厂家推荐:网带通过式抛丸机供应商怎么选 - 行业平台推荐
  • 从0到1打造专业职配助手:基于openJiuwen记忆库新特性的AI职业规划实战
  • 谷歌为 Pixel 推全新桌面模式
  • DSP、STM32、FPGA、ZYNQ U盘软件升级功能与软件开发源代码
  • 2026农业AI研讨会:破局与发展
  • 回溯算法 | part02 - 指南
  • 2026年评价高的多色金钻绒 厂家推荐:印花金钻绒/双色金钻绒销售厂家哪家好 - 行业平台推荐
  • AI已超越“猜词”,你还在旧认知里吗?
  • 权限覆盖与强制初始化
  • 连接池
  • 2026年热门的羽丝绒 工厂推荐:混纺丝绒/桑蚕丝绒/印花丝绒生产厂家推荐几家 - 行业平台推荐
  • Claude Code 推语音模式,AI 编程交互升级
  • flutter:使用listview
  • 2026新疆旅游终极攻略:四季玩法+10条黄金线路+42个避坑指南(辉澜牧歌权威出品) - 户外密码
  • 智元开源灵渠OS,具身智能生态再升级