MyBatis-Plus 批量操作 SQL 日志不打印问题解决方案
书接上文SqlLogInterceptor mybatis配置打印SQL问题描述在使用 MyBatis-Plus 的saveBatch()和updateBatchById()方法进行批量数据操作时发现自定义的 Druid SQL 日志拦截器SqlLogInterceptor无法打印这些批量操作的 SQL 语句导致调试和问题排查困难。问题分析1. 批量操作的特殊性MyBatis-Plus 的批量操作方法如saveBatch、updateBatchById使用了 JDBC 的批处理Batch模式来提高性能。批处理模式与普通的单条 SQL 执行方式不同普通执行每条 SQL 单独执行会触发statementExecuteAfter、statementExecuteUpdateAfter等方法批处理执行多条 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.java的statementExecuteBatchAfter方法添加 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 namecom.hzys.mapper levelDEBUG/优点可以看到每条 SQL 的详细参数MyBatis 原生支持稳定可靠缺点日志格式与自定义拦截器不一致日志量较大方案三双管齐下最佳实践同时使用方案一和方案二既能保证批量操作的 SQL 被记录又能在需要时查看详细的参数信息。完整代码示例1. 修改后的 SqlLogInterceptorSlf4j 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(); ListObject 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 version1.0 encodingUTF-8? configuration scantrue scanPeriod60 seconds !-- 其他配置... -- !-- 日志输出级别 -- root levelINFO appender-ref refSTDOUT/ appender-ref refINFO/ appender-ref refWARN/ appender-ref refERROR/ /root !-- MyBatis SQL 日志 -- logger namecom.hzys.mapper levelDEBUG/ logger namenet.sf.ehcache levelINFO/ logger namedruid.sql levelINFO/ /configuration3. 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注意事项性能考虑DEBUG 级别的 MyBatis 日志会输出大量信息生产环境建议关闭或设置为 INFO 级别日志过滤可以在SqlLogInterceptor中添加过滤逻辑避免打印某些不需要的 SQL如健康检查批量大小MyBatis-Plus 默认批量大小为 1000可以通过配置调整事务管理批量操作需要在事务中执行确保添加Transactional注解总结MyBatis-Plus 批量操作 SQL 不打印的问题主要是因为批处理模式使用了不同的执行路径原有的拦截器没有处理statementExecuteBatchAfter方法。通过修改拦截器并配合 MyBatis 原生日志可以完美解决这个问题既能保证日志的完整性又能在需要时查看详细的执行信息。

相关新闻

【开题答辩全过程】以 高校学生社团管理系统为例,包含答辩的问题和答案

【开题答辩全过程】以 高校学生社团管理系统为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人,语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

2026/7/6 1:24:15 阅读更多 →
聊聊 AI 的 token 到底是啥?

聊聊 AI 的 token 到底是啥?

想象一下,你和一个外国朋友语言不通,需要找翻译。 这个翻译有个特别的规则:不按“字”算钱,而是按“词块”算钱。 你说:“今天天气真好”, 翻译会拆成: 今天 / 天气 / 真 / 好 这些一个个“小块…

2026/7/7 0:50:05 阅读更多 →
90后AI大牛离职,创业潮要来了?

90后AI大牛离职,创业潮要来了?

3月4日,千问核心缔造者林俊旸宣布卸任,引发AI圈震动。千问发展势头正盛,他为何突然离开?后续又将走向何方?天才出走,AI圈震动1993年出生的林俊旸是阿里最年轻的P10,也是千问大模型的关键人物。在…

2026/7/5 6:56:32 阅读更多 →

最新新闻

上行与下行带宽区别

上行与下行带宽区别

很多朋友在测速时,都会看到两个数字:下载速度和上传速度。有时候,下载速度可以达到900Mbps,而上传速度却只有100Mbps左右;还有些企业申请专线时,运营商会特别说明“上下行对等带宽”。不少人虽然每天都在用…

2026/7/7 12:07:17 阅读更多 →
高压隔离技术:ISOM8710与STM32L4A6RG的安全设计实践

高压隔离技术:ISOM8710与STM32L4A6RG的安全设计实践

1. 高压安全隔离技术概述在工业自动化、医疗设备和电力电子等领域,高压电路与低压控制系统的安全隔离是确保人员和设备安全的关键需求。ISOM8710作为一款高性能数字隔离器,配合STM32L4A6RG低功耗微控制器,能够构建可靠的高压隔离解决方案。典…

2026/7/7 12:07:17 阅读更多 →
听歌不想只发截图?用MusicCard把喜欢的歌做成专属音乐海报

听歌不想只发截图?用MusicCard把喜欢的歌做成专属音乐海报

文章目录1.在极空间一键部署Music Card1.1ssh远程连接到极空间1.2安装docker1.3安装Music Card2.安装cpolar实现随时随地开发3.配置公网地址4.保留固定公网地址总结有些歌适合单曲循环,也适合被认真分享。只是很多时候,我们想表达的不只是“我在听这首歌…

2026/7/7 12:03:16 阅读更多 →
网盘直链下载助手完整指南:告别限速,一键获取真实下载链接

网盘直链下载助手完整指南:告别限速,一键获取真实下载链接

网盘直链下载助手完整指南:告别限速,一键获取真实下载链接 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国…

2026/7/7 11:57:12 阅读更多 →
如何免费解锁WeMod专业版:3个关键步骤与安全指南

如何免费解锁WeMod专业版:3个关键步骤与安全指南

如何免费解锁WeMod专业版:3个关键步骤与安全指南 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/gh_mirrors/we/Wand-Enhancer 还在为WeMod专业版的高昂订阅费而烦恼吗&am…

2026/7/7 11:55:12 阅读更多 →
腾讯混元 Hy3 开源:多任务比肩大尺寸模型,多项指标显著优化

腾讯混元 Hy3 开源:多任务比肩大尺寸模型,多项指标显著优化

【导语:继 4 月底发布 Hy3 preview 后,腾讯混元 Hy3 现已开源,在推理、智能体、长上下文等任务上显著进步,多项指标优化明显,成为高性价比可靠选择。】混元 Hy3 开源:多维度升级亮点足 腾讯混元 Hy3 在发布…

2026/7/7 11:53:12 阅读更多 →

日新闻

鸿蒙新特性:图片画廊与轮播导航——构建沉浸式图片浏览体验

鸿蒙新特性:图片画廊与轮播导航——构建沉浸式图片浏览体验

图片浏览是移动应用中最高频的场景之一。从社交应用的照片流到电商平台的商品图集,从旅游应用的景点相册到摄影作品展示——用户对图片浏览的体验要求不断提高:流畅的切换动画、直观的缩略图导航、便捷的收藏操作、自动播放模式。HarmonyOS NEXT ArkUI 虽…

2026/7/7 0:05:16 阅读更多 →
24V DC-DC降压芯片PW2312B/PW2815,SOT23-6到SOP8-EP方案对比

24V DC-DC降压芯片PW2312B/PW2815,SOT23-6到SOP8-EP方案对比

24V稳压芯片完整选型指南 PW8600 PW75XX PW2815 PW2312B LDODC/DC全方案 一、24V稳压方案概述 24V直流电源在工业自动化、门禁系统、电梯控制、汽车电子、LED驱动、监控设备等场景中应用极广,是最常见的中压直流母线电压。要将24V母线稳定降压至下游MCU、传感器…

2026/7/7 0:05:16 阅读更多 →
RAG+知识图谱混合检索与Graph RAG核心对比

RAG+知识图谱混合检索与Graph RAG核心对比

做企业RAG落地的团队,往往容易卡在一容易踩坑的选型难题: 当需求单纯靠向量RAG搞不定、单纯靠知识图谱也搞不定,必须同时依赖「文本语义理解 实体关系推理」时,到底是做「向量图谱混合检索」就够了,还是必须上「Grap…

2026/7/7 0:07:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/6 6:52:56 阅读更多 →

月新闻