【动态修改SQL语句】Mybatis拦截器修改sql语句

拦截器介绍

MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

1、Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2、ParameterHandler (getParameterObject, setParameters)
3、ResultSetHandler (handleResultSets, handleOutputParameters)
4、StatementHandler (prepare, parameterize, batch, update, query)
我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。

总体概括为:

1、拦截执行器的方法
2、拦截参数的处理
3、拦截结果集的处理
4、拦截Sql语法构建的处理

拦截器使用

拦截sql并增强

import com.cq.controller.InterceptAnnotation;
import lombok.extern.log4j.Log4j2;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Properties;
 
/**
 * sql拦截器,通过mybatis提供的Interceptor接口实现
 */
@Log4j2
@Component
//拦截StatementHandler类中参数类型为Statement的prepare方法(prepare=在预编译SQL前加入修改的逻辑)
//即拦截 Statement prepare(Connection var1, Integer var2) 方法
@Intercepts({
    
    @Signature(type = StatementHandler.class, method = "prepare", args = {
    
    Connection.class, Integer.class})})
public class MySqlInterceptor implements Interceptor {
    
    
 
 
    /**
     * 拦截sql
     *
     * @param invocation
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
 
        // 通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
        // 优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                new DefaultReflectorFactory());
 
        // 先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
 
        // id为执行的mapper方法的全路径名,如com.cq.UserMapper.insertUser, 便于后续使用反射
        String id = mappedStatement.getId();
        // sql语句类型 select、delete、insert、update
        String sqlCommandType = mappedStatement.getSqlCommandType().toString();
        // 数据库连接信息
//        Configuration configuration = mappedStatement.getConfiguration();
//        ComboPooledDataSource dataSource = (ComboPooledDataSource)configuration.getEnvironment().getDataSource();
//        dataSource.getJdbcUrl();
 
        BoundSql boundSql = statementHandler.getBoundSql();
        // 获取到原始sql语句
        String sql = boundSql.getSql().toLowerCase();
        log.info("SQL:{}", sql);
 
 
        // 增强sql
        // 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
        String mSql = sqlAnnotationEnhance(id, sqlCommandType, sql);
        // 直接增强sql
//      mSql = sql + " limit 2";
 
        //通过反射修改sql语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, mSql);
        log.info("增强后的SQL:{}", mSql); // 打印:增强后的SQL:select * from scenario_storage limit 2
        return invocation.proceed();
    }
 
    /**
     * 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
     * @param id 方法全路径
     * @param sqlCommandType sql类型
     * @param sql 所执行的sql语句
     */
    private String sqlAnnotationEnhance(String id, String sqlCommandType, String sql) throws ClassNotFoundException {
    
    
        // 通过类全路径获取Class对象
        Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf(".")));
        // 获取当前所拦截的方法名称
        String mName = id.substring(id.lastIndexOf(".") + 1);
        // 遍历类中所有方法名称,并if匹配上当前所拦截的方法
        for (Method method : classType.getDeclaredMethods()) {
    
    
            if (mName.equals(method.getName())) {
    
    
                // 判断方法上是否带有自定义@InterceptAnnotation注解
                InterceptAnnotation interceptorAnnotation = method.getAnnotation(InterceptAnnotation.class);
                if (interceptorAnnotation.flag()) {
    
    
                    if ("SELECT".equals(sqlCommandType)) {
    
    
                        // 增强sql
                        return sql + " limit 2";
                        // select * from scenario_storage limit 2
                    }
                }
            }
        }
        return sql;
    }
 
    @Override
    public Object plugin(Object target) {
    
    
        if (target instanceof StatementHandler) {
    
    
            return Plugin.wrap(target, this);
        } else {
    
    
            return target;
        }
    }
 
    @Override
    public void setProperties(Properties properties) {
    
    
    }
}

测试mapper接口

public interface IntercaperMapper {
    
    
 
    @InterceptAnnotation
    @Select("select * from scenario_storage")
    List<ScenarioStorageEntity> queryScenarioStorageAll();
}

实体类

import lombok.Data;
 
@Data
public class ScenarioStorageEntity {
    
    
 
    private int id;
    private String name;
 
}

自定义sql增强注解

/**
 * sql增强注解
 */
@Target({
    
    ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InterceptAnnotation {
    
    
 
    String value() default "";
 
    /** true增强、false忽略 */
    boolean flag() default true;
 
}

实践:用户只能看到自己相关的数据(通过mysql拦截器实现)跳转

猜你喜欢

转载自blog.csdn.net/qq_36881887/article/details/126232129