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方法
}

执行阶段

  1. 在以下操作之前执行
  • SQL 语句准备(StatementHandler.prepare)

  • 参数设置(ParameterHandler.setParameters)

  • JDBC 实际执行(Statement.execute)

  1. 在以下操作之后执行
  • MyBatis 缓存检查(二级缓存)
  • SQL 语句解析(但可以修改最终的 BoundSql)

应用场景

  1. 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;
    }
    
  2. 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();
    }
    
  3. 多租户数据过滤

    @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();
    }
    
  4. 获取完整 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;
    }
    
  5. 分页查询增强

    @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();
    }
    

测试使用

  1. 目标

修改sql,拼接task.task_id IN (xx,xx,xx)。

  1. 代码
# 实现拦截器
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();
    }
}
  1. 结果

image

posted @ 2025-08-18 08:35  南翔技校毕业后  阅读(50)  评论(0)    收藏  举报