一.背景
在很多业务场景下我们需要去拦截sql,达到不入侵原有代码业务处理一些东西,比如:分页操作,数据权限过滤操作,SQL执行时间性能监控等等,这里我们就可以用到Mybatis的拦截器Interceptor

二.Mybatis核心对象介绍
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:

Configuration 初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如,插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中
SqlSessionFactory SqlSession工厂
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息
三. Mybatis执行概要图

四.实现如下:

package com.aimin.dal.interceptor;import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.util.JdbcConstants;
import com.enn.common.util.OperatorInfoUtil;
import com.enn.common.vo.UserInfo;
import com.enn.dal.config.MapperConfig;
import com.enn.dal.constants.MybatisConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
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.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.google.common.cache.Cache;import java.sql.Connection;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** @author zlq* @date 2021/11/11* @description: Mybatis拦截器插件,拦截查询语句**/
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Component
public class MybatisInterceptor implements Interceptor {private static final String COLUMN_ENT_ID = "ent_id";private static final Pattern PATTERN_CONDITION_TENANT_ID = Pattern.compile(COLUMN_ENT_ID + " *=", Pattern.DOTALL);private static final Pattern PATTERN_MAIN_TABLE_ALIAS = Pattern.compile("(?i).*from\\s+[a-z_]+\\s+(?:as\\s+)?((?!(left|right|full|inner|join|where|group|order|limit))[a-z]+).*", Pattern.DOTALL);private static final Pattern PATTERN_CT_TABLE_SQL = Pattern.compile("(?i).*from *car_", Pattern.DOTALL);private static final Pattern PATTERN_COUNT_SQL = Pattern.compile("(?i) *select *count\\(.*\\) *from *\\((.*)\\) table_count$", Pattern.DOTALL);@Autowiredprivate Cache<String, MapperConfig> mybatisInterceptorCache;@Value(MybatisConstants.INCLUDED_MAPPER_IDS)private Set<String> includedMapperIds;@Overridepublic Object intercept(Invocation invocation) throws Throwable {StatementHandler statementHandler = (StatementHandler) invocation.getTarget();MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());/*从RoutingStatementHandler中获得处理对象PreparedStatementHandler,从这个对象中获取Mapper中的xml信息* */MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {return invocation.proceed();}//配置文件中sql语句的idString sqlId = mappedStatement.getId();log.info("sqlId:{}", sqlId);// 跳过无需拦截的sqlif (CollectionUtils.isNotEmpty(includedMapperIds) && !includedMapperIds.contains(sqlId.substring(0, sqlId.lastIndexOf('.')))) {return invocation.proceed();}BoundSql boundSql = statementHandler.getBoundSql();String originalSql = boundSql.getSql().trim();if (StringUtils.isBlank(originalSql)) {return invocation.proceed();}MapperConfig mapperConfig = getMapperConfig(sqlId, originalSql);// 判断是否为业务表if (!mapperConfig.isCtTable()) {return invocation.proceed();}// 判断sql中是否已经添加了租户查询条件if (mapperConfig.isTenantIdCondition()) {return invocation.proceed();}//拼接租户相关信息final UserInfo userInfo = OperatorInfoUtil.getUserInfo();if (userInfo == null) {return invocation.proceed();}if (!StringUtils.isAlphanumeric(userInfo.getEntId())) {log.warn("非法的租户id或者租户id为空,entId:{},未能拼接租户查询条件", userInfo.getEntId());return invocation.proceed();}log.info("entId:{}", userInfo.getEntId());StringBuilder scopeCondition = new StringBuilder();if (StringUtils.isNotBlank(mapperConfig.getMainTableAlias())) {scopeCondition.append(mapperConfig.getMainTableAlias()).append(".");}scopeCondition.append(COLUMN_ENT_ID);scopeCondition.append(MybatisConstants.EQUAL);scopeCondition.append(MybatisConstants.SINGLE_QUOTATION_MARK);scopeCondition.append(userInfo.getEntId());scopeCondition.append(MybatisConstants.SINGLE_QUOTATION_MARK);String sql = addCondition(originalSql, scopeCondition.toString());try {SQLUtils.formatMySql(sql);originalSql = sql;} catch (ParserException e) {log.warn("动态添加SQL数据过滤条件失败:{}", sql);}log.info("sql:{}", originalSql);metaObject.setValue("delegate.boundSql.sql", originalSql);return invocation.proceed();}private MapperConfig getMapperConfig(String sqlId, String originalSql) {MapperConfig mapperConfig = mybatisInterceptorCache.getIfPresent(sqlId);if (mapperConfig == null) {mapperConfig = new MapperConfig();mapperConfig.setMainTableAlias(getMainTableAlias(originalSql));mapperConfig.setCtTable(isCtTable(originalSql));mapperConfig.setTenantIdCondition(hasTenantIdCondition(originalSql));mybatisInterceptorCache.put(sqlId, mapperConfig);}return mapperConfig;}private static String addCondition(String originalSql, String scopeCondition) {String notCountSql;if ((notCountSql = getNotCountSql(originalSql)) != null) {notCountSql = SQLUtils.addCondition(notCountSql, scopeCondition, JdbcConstants.MYSQL);return "select count(0) from(" + notCountSql + ") table_count";}return SQLUtils.addCondition(originalSql, scopeCondition, JdbcConstants.MYSQL);}private static boolean hasTenantIdCondition(String sql) {sql = removeLinefeed(sql);Matcher matcher = PATTERN_CONDITION_TENANT_ID.matcher(sql);return matcher.find();}/*** 获取主表别名** @param sql sql语句* @return 主表别名*/private static String getMainTableAlias(String sql) {sql = removeLinefeed(sql);String notCountSql = getNotCountSql(sql);if (notCountSql != null) {sql = notCountSql;}sql = removeStrInBrackets(sql);Matcher matcher = PATTERN_MAIN_TABLE_ALIAS.matcher(sql);if (matcher.find()) {return matcher.group(1);}return null;}private static boolean isCtTable(String sql) {sql = removeLinefeed(sql);Matcher matcher = PATTERN_CT_TABLE_SQL.matcher(sql);return matcher.find();}private static String getNotCountSql(String sql) {sql = removeLinefeed(sql);Matcher matcher = PATTERN_COUNT_SQL.matcher(sql);return matcher.find() ? matcher.group(1) : null;}private static String removeLinefeed(String str) {return str == null ? null : str.replaceAll(MybatisConstants.SEP_LINEFEED, StringUtils.EMPTY);}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}private static String removeStrInBrackets(String str) {if (str == null) {return null;}char[] arr = str.toCharArray();boolean left = false;int leftIndex = 0;int rightIndex = 0;int num = 0;for (int i = 0; i < arr.length; i++) {switch (arr[i]) {case '(':if (!left) {left = true;leftIndex = i;}num++;break;case ')':num--;break;default:}if (left && num == 0) {rightIndex = i;break;}}if (leftIndex < rightIndex) {str = str.substring(0, leftIndex) + str.substring(rightIndex + 1, arr.length);if (str.contains(MybatisConstants.LEFT_PARENTHESIS_EN)) {return removeStrInBrackets(str);} else {return str;}}return str;}
}

MyBatis 插件之拦截器(Interceptor),拦截查询语句相关推荐

  1. 从零开始SpringCloud Alibaba实战(59)——过滤器filter、拦截器interceptor、和AOP的区别与联系及应用

    文章目录 前言 过滤器 拦截器 过滤器与拦截器的区别 AOP(面向切面) 三者使用场景 Filter过滤器 Interceptor拦截器 Spring AOP拦截器 Filter与Intercepto ...

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

    Mybatis拦截器实现限制查询条数 问题:查询结果过大的sql导致服务慢,系统不稳定? 解决思路:拦截sql,对sql进行修改,添加limit条件,限制查询结果的条数. 实现: 1.使用Mybati ...

  3. springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)

    文章目录 今日内容 一.SSM整合[重点] 1 SSM整合配置 问题导入 1.1 SSM整合流程 1.2 SSM整合配置 1.2.1 创建工程,添加依赖和插件 1.2.2 Spring整合Mybati ...

  4. SpringBoot2整合Mybatis拦截器,拦截mapper接口的某个方法

    需求: 在执行某个动态sql时,where 子句,希望通过用户进行自定义查询条件,比如用户可以传入 "id > 100011 and name = '张三'" 的多条件表达式 ...

  5. (十六)ATP应用测试平台——java应用中的过滤器Filter、拦截器Interceptor、参数解析器Resolver、Aop切面,你会了吗?

    前言 过滤器Filter.拦截器Interceptor.参数解析器Resolver.Aop切面是我们应用开发中经常使用到的技术,到底该如何使用这些web附属功能, 本小节我们就分别介绍一下其各自的用法 ...

  6. spring过滤器Filter 、 拦截器Interceptor 、 切片Aspect 详解

    springboot 过滤器Filter vs 拦截器Interceptor vs 切片Aspect 详解 1 前言 最近接触到了过滤器和拦截器,网上查了查资料,这里记录一下,这篇文章就来仔细剖析下过 ...

  7. Flume-NG源码阅读之SourceRunner,及选择器selector和拦截器interceptor的执行

    在AbstractConfigurationProvider类中loadSources方法会将所有的source进行封装成SourceRunner放到了Map<String, SourceRun ...

  8. Struts2拦截器(Interceptor)原理详解

    1.    理解拦截器 1.1.    什么是拦截器: 拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作 ...

  9. struts2学习笔记--拦截器(Interceptor)和登录权限验证Demo

    理解 Interceptor拦截器类似于我们学过的过滤器,是可以在action执行前后执行的代码.是我们做web开发是经常使用的技术,比如权限控制,日志.我们也可以把多个interceptor连在一起 ...

  10. 【Spring MVC】自定义拦截器 Interceptor

    一.自定义拦截器 1.拦截器,跟过滤器比较像的技术. 1.1 拦截器和过滤器的区别: 拦截器只能拦Controller,Filter可以拦任何请求 因为Spring的入口是dispatcherServ ...

最新文章

  1. 用java实现给图片增加图片水印或者文字水印(也支持视频图像帧添加水印)
  2. 【转】iOS实时卡顿监控
  3. BZOJ1951: [Sdoi2010]古代猪文
  4. JSP RequestDispatcher servlet之间传参
  5. 严谨技术支撑vs奔放客户的100个真实写真
  6. linux中文件属性mtime,linux stat (三个时间属性命令可用来列出文件的 atime、ctime 和 mtime。)...
  7. storm的流分组策略
  8. 基于android的交流平台,基于Android的移动学习交流平台的设计与实现
  9. 超详细:Springboot连接centos7下redis6的必要配置和失败分析
  10. Struts2体系介绍
  11. MS .Net常见的持久层框架
  12. 大学实训_软件毕设_Java入门实战_商场管理系统_Punrain
  13. love2d教程10--粒子效果
  14. Java程序员必读书籍推荐
  15. Spring启动,constructor,@PostConstruct,afterPropertiesSet,onApplicationEvent执行顺序 原创 2016年09月29日 11:39:2
  16. ConcurrentHashMap1.8 源码分析
  17. uni-app 中如何使用谷歌地图 !?
  18. 查询一段时间内的具体时间
  19. jQuery基础知识(黑马程序员前端基础必备教程视频笔记)
  20. php实现魔方变换颜色,php魔方方法

热门文章

  1. HTTPS证书过期|SSL证书过期 |华硕路由器| NAS|Certtificate is not valid
  2. Mysql查看表的数据量
  3. (转载)C++中的头文件
  4. 【附源码】计算机毕业设计SSM社区生鲜电商平台
  5. 冒泡排序法(C语言实现)
  6. setup factory打包工具的使用
  7. 有什么好用的gif制作软件 制作GIF表情包教程
  8. Linux 如何添加一个 Swap 文件
  9. PHP乱码问题,UTF-8(乱码)
  10. php代码解决乱码问题