今天出了一个问题,明明 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("========================================"); } }
浙公网安备 33010602011771号