惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 黄洪波

安装openclaw ModelAttribute 老革命遇上新问题 使用calcite构造ddl建表语句 OpenWebUI单点登录之解决动态参数问题 IDEA自带的Maven 3.9.x无法刷新http nexus私服(转) windows docker安装rocketmq之踩坑记 分布式系统设计经典论文(转载) 训练自己的yolo-v11数据集(二) 训练自己的yolo-v11数据集(一) 本地使用pycharm进行yolo推理 2024年的云原生架构需要哪些技术栈 (转) yolo v11学习,入门篇 - 黄洪波 OpenWebUI单点登录之深坑 AI工具验证 解决Win10无法进入睡眠模式(转) idea常用插件 内网离线模式下激活JRebel java导入json数据至doris 将SpringBoot打包之后的jar设为守护进程
排查项目中依赖的mybatis 拦截器
黄洪波 · 2026-02-04 · via 博客园 - 黄洪波

今天出了一个问题,明明 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("========================================");
    }
}