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

捕获Mybatis执行的Sql

使用

// 开启捕获
SqlCaptureInterceptor.startCapture();
boolean success = SqlHelper.exeBatch(sqlSessionFactory, updateList, OrderItemMapper.class, OrderItemMapper::updateById);
// 获取捕获的sql并且清空
List<String> sqlList = SqlCaptureInterceptor.getCapturedSqlAndClear();
System.out.println(sqlList);

原理

利用 Mybatis 的拦截器来捕获 Sql,然后格式化为完整的 Sql。

源码

SqlParser.class

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;/*** Sql 解析器** @author Jalon* @since 2025/9/23 12:59**/
public class SqlParser {// 日期格式化器private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");/*** 从BoundSql中提取参数值列表*/public static List<Object> getParameterValues(Configuration configuration, BoundSql boundSql) {List<Object> parameterValues = new ArrayList<>();Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null && !parameterMappings.isEmpty()) {MetaObject metaObject = parameterObject != null ?configuration.newMetaObject(parameterObject) : null;for (ParameterMapping mapping : parameterMappings) {String property = mapping.getProperty();if (boundSql.hasAdditionalParameter(property)) {parameterValues.add(boundSql.getAdditionalParameter(property));} else if (parameterObject != null && metaObject.hasGetter(property)) {parameterValues.add(metaObject.getValue(property));} else {parameterValues.add(null);}}}return parameterValues;}/*** 使用JSQLParser替换SQL中的参数占位符*/public static String replaceParametersWithJsqlparser(String sql, List<Object> parameterValues) {try {// 解析SQL生成抽象语法树Statement statement = CCJSqlParserUtil.parse(sql);// 创建参数迭代器Iterator<Object> parameterIterator = parameterValues.iterator();// 创建字符串缓冲区StringBuilder sb = new StringBuilder();// 创建表达式解析器,用于替换?占位符ExpressionDeParser expressionDeParser = new ExpressionDeParser() {@Overridepublic void visit(net.sf.jsqlparser.expression.JdbcParameter jdbcParameter) {if (parameterIterator.hasNext()) {Object value = parameterIterator.next();buffer.append(formatParameterValue(value));} else {buffer.append("?"); // 参数不足时保留占位符}}};// 创建SelectDeParserSelectDeParser selectDeParser = new SelectDeParser(expressionDeParser, sb);expressionDeParser.setSelectVisitor(selectDeParser);expressionDeParser.setBuffer(sb);// 根据语句类型使用不同的解析方式if (statement instanceof Select) {// 处理SELECT语句((Select) statement).getSelectBody().accept(selectDeParser);} else {// 处理非SELECT语句,使用三参数构造函数StatementDeParser statementDeParser = new StatementDeParser(expressionDeParser,selectDeParser,sb);statement.accept(statementDeParser);}return sb.toString();} catch (JSQLParserException e) {// 解析失败时返回原始SQL并记录日志System.err.println("SQL解析失败: " + e.getMessage() + ", 原始SQL: " + sql);return sql;}}/*** 格式化参数值为SQL中可用的字符串*/public static String formatParameterValue(Object value) {if (value == null) {return "NULL";}// 处理字符串类型if (value instanceof String || value instanceof Character) {String strValue = value.toString().replace("'", "''"); // 转义单引号return "'" + strValue + "'";}// 处理日期类型if (value instanceof java.sql.Date) {return "'" + DATE_FORMAT.format(value) + "'";}if (value instanceof java.util.Date) {return "'" + DATETIME_FORMAT.format(value) + "'";}// 处理布尔类型if (value instanceof Boolean) {return (Boolean) value ? "1" : "0";}// 处理数组和集合类型if (value.getClass().isArray()) {return formatArray((Object[]) value);}if (value instanceof Collection) {return formatCollection((Collection<?>) value);}// 其他类型直接返回字符串表示return value.toString();}/*** 格式化数组类型参数*/public static String formatArray(Object[] array) {StringBuilder sb = new StringBuilder();sb.append("(");for (int i = 0; i < array.length; i++) {if (i > 0) {sb.append(", ");}sb.append(formatParameterValue(array[i]));}sb.append(")");return sb.toString();}/*** 格式化集合类型参数*/public static String formatCollection(Collection<?> collection) {StringBuilder sb = new StringBuilder();sb.append("(");boolean first = true;for (Object item : collection) {if (!first) {sb.append(", ");}sb.append(formatParameterValue(item));first = false;}sb.append(")");return sb.toString();}
}

SqlCaptureInterceptor.class

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;import java.util.ArrayList;
import java.util.List;
import java.util.Properties;/*** Sql捕获拦截器** @author Jalon* @since 2025/9/23 11:28**/
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class})
})
public class SqlCaptureInterceptor implements Interceptor {// 最大记录private static final int DEFAULT_MAX_LEN = 50;// 线程局部变量存储捕获的SQLprivate static final ThreadLocal<List<String>> sqlListHolder = ThreadLocal.withInitial(ArrayList::new);// 控制是否开启捕获的开关private static final ThreadLocal<Boolean> captureEnabled = ThreadLocal.withInitial(() -> false);public static int maxLength = DEFAULT_MAX_LEN;/*** 开启SQL捕获*/public static void startCapture() {captureEnabled.set(true);// 清空历史数据sqlListHolder.get().clear();}/*** 停止SQL捕获*/public static void stopCapture() {captureEnabled.set(false);}/*** 获取捕获的SQL列表*/public static List<String> getCapturedSql() {return new ArrayList<>(sqlListHolder.get()); // 返回副本,避免外部修改}/*** 获取当前线程执行的SQL列表并清空捕获的SQL数据*/public static List<String> getCapturedSqlAndClear() {ArrayList<String> sqls = new ArrayList<>(sqlListHolder.get());SqlCaptureInnerInterceptor.clear();return sqls;}/*** 清除捕获的SQL数据*/public static void clear() {sqlListHolder.get().clear();captureEnabled.remove();sqlListHolder.remove();}@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 仅在开启捕获时记录SQLtry {if (Boolean.TRUE.equals(captureEnabled.get())) {if (sqlListHolder.get().size() >= maxLength) {// 超长移除第一个sqlListHolder.get().remove(0);}MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs().length > 1 ? invocation.getArgs()[1] : null;// 获取SQL信息BoundSql boundSql = mappedStatement.getBoundSql(parameter);Configuration configuration = mappedStatement.getConfiguration();// 获取完整的sqlList<Object> parameterValues = SqlParser.getParameterValues(configuration, boundSql);String fullSql = SqlParser.replaceParametersWithJsqlparser(boundSql.getSql(), parameterValues);sqlListHolder.get().add(fullSql);}} catch (Throwable throwable) {System.err.println("SQL解析失败: " + throwable.getMessage());}// 继续执行原方法return invocation.proceed();}@Overridepublic Object plugin(Object target) {// 只对Executor类型进行拦截if (target instanceof Executor) {return Plugin.wrap(target, this);}return target;}@Overridepublic void setProperties(Properties properties) {// 可以通过配置文件传递参数}
}

配置

对于SpringMVC 项目来说,应该在 mybatis 的 xml 文件中添加 plugin,来注册拦截器。
SpringBoot 项目的话需要再 MyBatisConfig 类中配置,参考:

mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL MAP Config 3.1//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><plugins><plugin interceptor="com.xx.interceptor.SqlCaptureInterceptor"></plugin></plugins>
</configuration>

MyBatisConfig.class

import com.fly.ssm.interceptor.SqlCaptureInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;@Configuration
public class MyBatisConfig {@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);// 添加拦截器Interceptor[] plugins = new Interceptor[1];plugins[0] = new SqlCaptureInterceptor();sessionFactory.setPlugins(plugins);return sessionFactory.getObject();}
}
http://www.dtcms.com/a/397926.html

相关文章:

  • Kubernetes 进阶实战:CRD、Gateway API 与优先级调度
  • Netdata系统监控:30秒定位服务器故障的实践方法
  • 制作 Bash Shell 方式的软件发布安装包的原理和方法
  • 网站标题怎么做链接云服务器建设网站教程
  • 栈-堆理解题(c++)
  • 江西同为科技有限公司亮相2025北京国际两用先进技术装备展览会 —— 致力于电气联接与保护,推动两用技术融合发展
  • 网站首页幻灯片不显示知更鸟wordpress主题
  • React 基础:快速掌握 State、事件和 Hook
  • 烟台网站制作计划wordpress怎么修改logo尺寸
  • Apache Hive 如何在大数据中发挥能量
  • CSS——实现盒子在页面居中
  • RocketMQ相对于RabbitMQ 的优势
  • ELK 企业级日志分析系统(完整版)
  • WaveTerminal+cpolar:提升远程协作效率的开发利器
  • 【记录】Ubuntu系统实现从远程服务器上传下载文件
  • 通过串口控制RDA5807收音模块(stm32+c#上位机)
  • hive表元数据修复脚本
  • React中的Hook
  • React简单例子
  • Playwright MCP 服务器对比高层级的 MCP 服务器解决方案
  • app下载网站模板wordpress将公网ip改为域名
  • 个人做网站如何推广安全优化大师
  • jupyter notebook用简易python代码跑本地模型
  • Android 安卓RIL介绍
  • 开源 java android app 开发(十五)绘图定义控件--仪表盘
  • Android如何自动弹出软键盘?
  • Linux Shell 脚本:从零到进阶的实战笔记
  • MR 一体机市场报告:2031全球规模突破 1.98亿美元,中国 40.8% 市占率成核心增长极
  • 网站管理员权限权重高的网站有哪些
  • 【Spark+Hive+hadoop】基于spark+hadoop基于大数据的全球用水量数据可视化分析系统大数据毕设