Mybatis拦截器实现限制查询条数

问题:查询结果过大的sql导致服务慢,系统不稳定?

解决思路:拦截sql,对sql进行修改,添加limit条件,限制查询结果的条数。

实现:

1、使用Mybatis拦截器。

将拦截器类交给spring管理,使用配置文件、配置类、或直接使用@Component注解均可。

目的都是将拦截器类注入spring容器中。

任选一种配置方式

1.1 配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> 
    <plugins>
  	<plugin interceptor="com.qxy.mybatis.interceptor.TableLimitInterceptor"/>
 </plugins>
 
</configuration>

1.2 配置类:

@Configuration
public class MyBatisConfiguration {
    @Bean
    TableLimitInterceptor tableLimitInterceptor(){
        return new TableLimitInterceptor();
    }
}

1.3 @Component注解:(直接在拦截器类上添加注解)

2、Mybatis拦截器实现代码

实现org.apache.ibatis.plugin.Interceptor接口,重写以下方法:

public interface Interceptor {

  // 拦截器拦截后对象后,执行自己的业务逻辑
  Object intercept(Invocation invocation) throws Throwable;
    
  // 判断是否拦截这个类型对象(根据@Intercepts注解决定),然后决定是返回一个代理对象还是返回原对象。
  Object plugin(Object target);

  // 可给拦截器设置一些变量对象
  void setProperties(Properties properties);

}
@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class TableLimitInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(ChannelBusiProcessorFactory.class);

    // 拦截器拦截后对象后,执行自己的业务逻辑
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        
		// 入参invocation指拦截到的对象
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        MySqlStatementParser mySqlStatementParser = new MySqlStatementParser(sql);
        SQLStatement statement = mySqlStatementParser.parseStatement();

        if (statement instanceof SQLSelectStatement) {
            SQLSelect selectQuery = ((SQLSelectStatement) statement).getSelect();
            MySqlSelectQueryBlock sqlSelectQuery = (MySqlSelectQueryBlock) selectQuery.getQuery();

            String tableName = sqlSelectQuery.getFrom().toString();
            SQLLimit limit = sqlSelectQuery.getLimit();
            // 不拦截 带有limit 并且 表名中含有 dict的sql
            if(null == limit && !tableName.contains("dict") ){
                SQLLimit sqlLimit = new SQLLimit();
                sqlLimit.setRowCount(1000);
                sqlSelectQuery.setLimit(sqlLimit);
                String newSql = sqlSelectQuery.toString();
                // 修改 sql
                ReflectUtil.setFieldValue(boundSql, "sql", newSql);
            }
        }
        return invocation.proceed(); //程序继续运行
    }

    //判断是否拦截这个类型对象(根据@Intercepts注解决定),然后决定是返回一个代理对象还是返回原对象。
    //每经过一个拦截器对象都会调用插件的plugin方法,也就是说,该方法会调用4次。根据@Intercepts注解来决定是否进行拦截处理。
    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
        	return Plugin.wrap(target, this);
        }
    }
	
    // 可给拦截器设置一些变量对象
    @Override
    public void setProperties(Properties properties) {
    }
}

3、拦截器知识:

3.1 MyBatis四大核心对象(四个可以被拦截的对象):

ParameterHandler: 处理sql 参数对象

ResultsetHandler: 处理sql 返回结果集

StatementHandler: 数据库的处理对象,执行sql语句

Executor: MyBatis执行器, 用于执行增删改查操作

3.2

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})

@Intercepts
Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。

@Intercepts:标识该类是一个拦截器;
@Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;

type:上述四种对象类型中的一种;

method:对应接口中的哪类方法(因为可能存在重载方法);

args:对应哪一个方法的入参;

(注:type、method、args三个值共同来确定需要拦截的具体方法)

拦截类型拦截方法
Executorupdate, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandlergetParameterObject, setParameters
StatementHandlerprepare, parameterize, batch, update, query
ResultSetHandlerhandleResultSets, handleOutputParameters

总结:拦截器可拦截上述的任意一个或多个方法, 拦截到方法后,在方法中拿到对应的参数,可根据自己的业务逻辑对参数进行修改,实现自己的业务,非常的灵活。可同时使用多个拦截器,拦截多个不同的方法不会产生冲突,并且多个拦截器可同时拦截同一个方法,此时多个拦截器会依次执行,这里就要考虑拦拦截器执行的先后顺序了。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐