• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
黄洪波写点东西的地方
博客园    首页    新随笔    联系   管理    订阅  订阅
排查项目中依赖的mybatis 拦截器

今天出了一个问题,明明 romoveByIds in IdList 入参是正确的,最后的日志却是  update  xxx  where  id in (?)   paramater null
参数被替换成了null

故需要排查项目中注入了哪些MybatisIntercepter

@Autowired
private List<MybatisPlusInterceptor> interceptors;

@PostConstruct
public void logInterceptors() {
    for (MybatisPlusInterceptor interceptor : interceptors) {
        // 反射或者通过打断点看其内部的 innerInterceptors 列表
        System.out.println("当前拦截器链: " + interceptor);
    }
}

启动时会打印所有拦截链

 
检查所有拦截器的前后参数列表
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * 拦截器调试代理
 */
@Slf4j
public class InnerInterceptorSpy implements InnerInterceptor {

    private final InnerInterceptor delegate;
    private final int index;

    public InnerInterceptorSpy(InnerInterceptor delegate, int index) {
        this.delegate = delegate;
        this.index = index;
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
                            RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
        throws SQLException {
        if (isTarget(ms)) {
            dump("beforeQuery", "BEFORE", boundSql, ms);
        }

        delegate.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);

        if (isTarget(ms)) {
            dump("beforeQuery", "AFTER ", boundSql, ms);
        }
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        // 从 StatementHandler 提取 MappedStatement 和 BoundSql
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        BoundSql boundSql = mpSh.boundSql();

        if (isTarget(ms)) {
            dump("beforePrepare", "BEFORE", boundSql, ms);
        }

        delegate.beforePrepare(sh, connection, transactionTimeout);

        // 重新获取(可能被修改了)
        boundSql = mpSh.boundSql();

        if (isTarget(ms)) {
            dump("beforePrepare", "AFTER ", boundSql, ms);
        }
    }

    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter)
        throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);

        if (isTarget(ms)) {
            dump("beforeUpdate", "BEFORE", boundSql, ms);
        }

        delegate.beforeUpdate(executor, ms, parameter);

        // 重新获取(可能被修改了)
        boundSql = ms.getBoundSql(parameter);

        if (isTarget(ms)) {
            dump("beforeUpdate", "AFTER ", boundSql, ms);
        }
    }

    /**
     * 判断是否是目标方法
     */
    private boolean isTarget(MappedStatement ms) {
        return ms.getId().contains("deleteByIds")
            || ms.getId().contains("deleteBatchIds");
    }

    private void dump(String method, String phase, BoundSql boundSql, MappedStatement ms) {
        String prefix = String.format("[%d] %-35s %s", index, delegate.getClass().getSimpleName(), phase);

        log.error("--- {} ---", prefix);
        log.error("{} SQL: {}", prefix, boundSql.getSql().replaceAll("\\s+", " "));

        List<org.apache.ibatis.mapping.ParameterMapping> mappings = boundSql.getParameterMappings();
        log.error("{} 参数映射统计: 共 {} 个参数位", prefix, mappings.size());

        for (int i = 0; i < mappings.size(); i++) {
            org.apache.ibatis.mapping.ParameterMapping mapping = mappings.get(i);
            String prop = mapping.getProperty();

            // 使用修正后的取值逻辑
            Object value = getParamValue(boundSql, ms, prop);

            log.error("{}   [位置:{}] 映射名: {} -> 实时值: {} ({})",
                prefix,
                i + 1,
                String.format("%-20s", prop),
                value,
                (value != null ? value.getClass().getSimpleName() : "null"));
        }
        log.error("--------------------------------------------------");
    }

    // 将 getParamValue 放入 Spy 类中作为私有辅助方法
    private Object getParamValue(BoundSql boundSql, MappedStatement ms, String prop) {
        // 1. 核心:必须先从 AdditionalParameters 找(foreach 的参数存放在这里)
        if (boundSql.hasAdditionalParameter(prop)) {
            return boundSql.getAdditionalParameter(prop);
        }

        Object parameterObject = boundSql.getParameterObject();
        if (parameterObject == null) {
            return null;
        }

        // 2. 处理简单类型(Long, String 等)
        if (ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
            // 如果映射名是 "value" 或 "id" 这种,但 parameterObject 是基本类型,直接返回对象本身
            return parameterObject;
        }

        // 3. 处理复杂对象或 Map
        try {
            return ms.getConfiguration().newMetaObject(parameterObject).getValue(prop);
        } catch (Exception e) {
            // 对于一些 MyBatis 动态生成的变量名,取值失败是正常的,记录下来即可
            return "[Path Not Found]";
        }
    }


    private void dumpParam(String method, String phase, BoundSql boundSql, MappedStatement ms) {
        String prefix = String.format("[%d] %-35s %s",
            index, delegate.getClass().getSimpleName(), phase);

        log.error("--- {} ---", prefix);
        log.error("{} SQL: {}", prefix, boundSql.getSql().replaceAll("\\s+", " "));

        List<ParameterMapping> mappings = boundSql.getParameterMappings();
        Object parameterObject = boundSql.getParameterObject();

        log.error("{} 参数映射统计: 共 {} 个参数位", prefix, mappings.size());

        for (int i = 0; i < mappings.size(); i++) {
            org.apache.ibatis.mapping.ParameterMapping mapping = mappings.get(i);
            String prop = mapping.getProperty();
            Object value = null;

            // 模拟 MyBatis 取值逻辑,查看当前映射是否还能取到值
            try {
                if (parameterObject == null) {
                    value = "null (Object is null)";
                } else if (ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject; // 基本类型
                } else {
                    // 从 MetaObject 中取值,这是 MyBatis 真实取值方式
                    value = ms.getConfiguration().newMetaObject(parameterObject).getValue(prop);
                }
            } catch (Exception e) {
                value = "[取值失败: " + e.getMessage() + "]";
            }

            log.error("{}   [第{}位] 映射名: {} -> 实时值: {} ({})",
                prefix, i + 1, prop, value, (value != null ? value.getClass().getSimpleName() : "null"));
        }
        log.error("--------------------------------------------------");
    }

    /**
     * 打印详细信息
     */
    private void dumpPrint(String method, String phase, BoundSql boundSql, MappedStatement ms) {
        log.error("╔════════════════════════════════════════════════════════════════");
        log.error("║ [{}] {} - {} {}", index, delegate.getClass().getSimpleName(), method, phase);
        log.error("╠════════════════════════════════════════════════════════════════");
        log.error("║ MappedStatement ID: {}", ms.getId());
        log.error("║ SQL: {}", boundSql.getSql());
        log.error("║ 参数对象: {}", boundSql.getParameterObject());
        log.error("║ 参数对象类型: {}",
            boundSql.getParameterObject() != null ? boundSql.getParameterObject().getClass().getName() : "null");

        log.error("║ 参数映射 (共{}个):", boundSql.getParameterMappings().size());
        for (int i = 0; i < boundSql.getParameterMappings().size(); i++) {
            log.error("║   [{}] {}", i, boundSql.getParameterMappings().get(i));
        }

        // 如果参数是 Map,打印详细内容
        if (boundSql.getParameterObject() instanceof java.util.Map) {
            java.util.Map<?, ?> map = (java.util.Map<?, ?>) boundSql.getParameterObject();
            log.error("║ Map 参数详情:");
            map.forEach((k, v) -> {
                log.error("║   {} = {} ({})", k, v, v != null ? v.getClass().getSimpleName() : "null");
            });
        }

        // 额外参数
        if (!boundSql.getAdditionalParameters().isEmpty()) {
            log.error("║ 额外参数:");
            boundSql.getAdditionalParameters().forEach((k, v) -> {
                log.error("║   {} = {}", k, v);
            });
        }

        log.error("╚════════════════════════════════════════════════════════════════\n");
    }
}
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * 拦截器调试安装器
 */
@Slf4j
@Component
public class MpInterceptorSpyInstaller {

    @Autowired
    private List<MybatisPlusInterceptor> interceptors;

    @PostConstruct
    public void install() throws Exception {
        log.warn("========================================");
        log.warn("开始安装拦截器调试代理");
        log.warn("========================================");

        for (int i = 0; i < interceptors.size(); i++) {
            MybatisPlusInterceptor mpi = interceptors.get(i);
            log.warn("处理拦截器链 {}: {}", i, mpi);

            Field f = MybatisPlusInterceptor.class.getDeclaredField("interceptors");
            f.setAccessible(true);

            @SuppressWarnings("unchecked")
            List<InnerInterceptor> list = (List<InnerInterceptor>) f.get(mpi);

            List<InnerInterceptor> wrapped = new ArrayList<>();
            for (int j = 0; j < list.size(); j++) {
                InnerInterceptor original = list.get(j);
                InnerInterceptor spy = new InnerInterceptorSpy(original, j);
                wrapped.add(spy);
                log.warn("  [{}] {} -> 已包装", j, original.getClass().getSimpleName());
            }

            list.clear();
            list.addAll(wrapped);
        }

        log.warn("========================================");
        log.warn("拦截器调试代理安装完成");
        log.warn("========================================");
    }
}

 

posted on 2026-02-04 08:57  黄洪波  阅读(2)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3