Mybatis-Interceptor插件之Executor-query方法
前言
MyBatis的Executor.query方法是SQL查询操作的核心入口点,通过拦截此方法可以实现各种强大的扩展功能。
@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    ),
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, 
               CacheKey.class, BoundSql.class}
    )
})
public class QueryInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 获取方法参数
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        
        // 2. 获取BoundSql(针对第二个重载方法)
        BoundSql boundSql = args.length > 4 ? (BoundSql) args[5] : ms.getBoundSql(parameter);
        
        // 3. 前置处理
        System.out.println("拦截的SQL: " + boundSql.getSql());
        System.out.println("Mapper方法: " + ms.getId());
        
        // 4. 执行原始查询
        Object result = invocation.proceed();
        
        // 5. 后置处理
        System.out.println("查询结果数量: " + (result instanceof List ? ((List)result).size() : 1);
        
        return result;
    }
    
    // ... plugin和setProperties方法
}
执行阶段
- 在以下操作之前执行
 
- 
SQL 语句准备(StatementHandler.prepare)
 - 
参数设置(ParameterHandler.setParameters)
 - 
JDBC 实际执行(Statement.execute)
 
- 在以下操作之后执行
 
- MyBatis 缓存检查(二级缓存)
 - SQL 语句解析(但可以修改最终的 BoundSql)
 
应用场景
- 
SQL 性能监控
@Override public Object intercept(Invocation invocation) throws Throwable { long start = System.currentTimeMillis(); Object result = invocation.proceed(); long end = System.currentTimeMillis(); // 记录慢查询 if (end - start > 500) { BoundSql boundSql = getBoundSql(invocation); log.warn("慢SQL[{}ms]: {}", (end - start), boundSql.getSql()); } return result; } - 
SQL 改写
@Override public Object intercept(Invocation invocation) throws Throwable { BoundSql boundSql = getBoundSql(invocation); String newSql = rewriteSql(boundSql.getSql()); // 使用反射修改BoundSql Field field = boundSql.getClass().getDeclaredField("sql"); field.setAccessible(true); field.set(boundSql, newSql); return invocation.proceed(); } - 
多租户数据过滤
@Override public Object intercept(Invocation invocation) throws Throwable { BoundSql boundSql = getBoundSql(invocation); String tenantId = TenantContext.getCurrentTenant(); if (!isIgnoredTable(boundSql)) { String newSql = addTenantCondition(boundSql.getSql(), tenantId); resetBoundSql(boundSql, newSql); } return invocation.proceed(); } - 
获取完整 SQL 语句
private String getCompleteSql(BoundSql boundSql, Object parameter) { String sql = boundSql.getSql(); List<ParameterMapping> mappings = boundSql.getParameterMappings(); if (mappings != null && parameter != null) { for (ParameterMapping mapping : mappings) { Object value = getParameterValue(mapping.getProperty(), parameter); sql = sql.replaceFirst("\\?", "'" + String.valueOf(value) + "'"); } } return sql; } - 
分页查询增强
@Override public Object intercept(Invocation invocation) throws Throwable { Object parameter = invocation.getArgs()[1]; if (parameter instanceof PageParam) { PageParam page = (PageParam) parameter; RowBounds rowBounds = new RowBounds(page.getOffset(), page.getLimit()); invocation.getArgs()[2] = rowBounds; // 修改RowBounds参数 // 添加COUNT查询 if (page.isNeedTotal()) { executeCountQuery(invocation); } } return invocation.proceed(); } 
测试使用
- 目标
 
修改sql,拼接task.task_id IN (xx,xx,xx)。
- 代码
 
# 实现拦截器
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReflectUtil;
import com.zq.common.core.domain.TaskIdQuery;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import org.apache.ibatis.cache.CacheKey;
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.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class StationPermissionInterceptor implements Interceptor {
    private final static Set<String> set = new HashSet<>();
    static {
        set.add("com.zq.mes.craft.mapper.ExecTaskMapper.test2Page_mpCount");
        set.add("com.zq.mes.craft.mapper.ExecTaskMapper.test2Page");
    }
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] params = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) params[0];
        if (set.contains(mappedStatement.getId())) {
            Object parameter = params[1];
            if (parameter instanceof Map) {
                Map<?, ?> paramMap = (Map<?, ?>) parameter;
                Object idValue = paramMap.get("query");
                TaskIdQuery query = (TaskIdQuery) idValue;
                Set<Long> taskIds = query.getTaskIds();
                if (CollectionUtil.isEmpty(taskIds)) {
                    return invocation.proceed();
                }
                BoundSql boundSql = (BoundSql) params[5];
                String taskIdsSQL = convertSetToSqlString(taskIds);
                String originalSql = boundSql.getSql();
                String whereClause = " WHERE task.task_id IN ( " + taskIdsSQL + " )";
                String newSql;
                if (originalSql.contains("WHERE")) {
                    newSql = originalSql.replace("WHERE", whereClause + " AND ");
                } else if (originalSql.contains("where")) {
                    newSql = originalSql.replace("where", whereClause + " and ");
                } else {
                    newSql = originalSql + whereClause;
                }
                // 使用反射修改SQL
                ReflectUtil.setFieldValue(boundSql, "sql", newSql);
            }
        }
        return invocation.proceed();
    }
    public String convertSetToSqlString(Set<Long> ids) {
        ExpressionList expressionList = new ExpressionList();
        ids.forEach(id -> expressionList.addExpressions(new LongValue(id)));
        return expressionList.toString();
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
        // NOP
    }
}
# 添加插件
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
@RequiredArgsConstructor
public class MybatisPlusConfig {
    @Bean
    public StationPermissionInterceptor stationPermissionInterceptor() {
        // 自定义工站权限【Interceptor】
        return new StationPermissionInterceptor();
    }
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        // 乐观锁插件
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
        // 阻断插件
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
        // 自定义工站权限【InnerInterceptor】
//        interceptor.addInnerInterceptor(new StationPermissionInnerInterceptor());
        return interceptor;
    }
    /**
     * 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html
     */
    public PaginationInnerInterceptor paginationInnerInterceptor() {
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        // 设置数据库类型为mysql
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInnerInterceptor.setMaxLimit(-1L);
        return paginationInnerInterceptor;
    }
    /**
     * 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html
     */
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }
    /**
     * 如果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html
     */
    public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
        return new BlockAttackInnerInterceptor();
    }
}
- 结果
 

                    
                
                
            
        
浙公网安备 33010602011771号