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号