项目中途遇到业务需求更改,在查询某张表时需要增加条件,由于涉及的sql语句多而且依赖其他服务的jar,逐个修改sql语句和接口太繁杂。项目使用mybatis框架,因此借鉴PageHelper插件尝试使用mybatis的Interceptor来实现改需求。
总体思路:
  • 从BoundSql中获取sql,通过正则匹配替换表名为子查询REPLACE_TXT
  • 添加子查询REPLACE_TXT 中需要用到的参数到mybatis参数列表中
  • 添加参数与占位符映射,即添加ParameterMapping对象到ParameterMappings中,由于statement在执行时是按照ParameterMappings的元素索引定位占位符封装参数(即ParameterMappings中的第一个参数会封装到第一个占位符上),因此ParameterMappings中的参数顺序需要和占位符保持一致。其次ParameterMappings的元素个数需要和占位符个数保持一致。
  • 为了保证该intercept在最后执行,使用AutoConfiguration将intercept添加到SqlSessionFactory的Configuration中,并在spring.factories文件中添加AutoConfiguration
  • 未测试性能以及是否存在未知缺陷
1、Interceptor 代码实现
package org.cnbi.project.other.sql.intercept;import cn.hutool.core.util.NumberUtil;
import com.cnbi.cloud.common.core.exception.ServiceException;
import com.github.pagehelper.Page;
import com.github.pagehelper.util.ExecutorUtil;
import com.github.pagehelper.util.MetaObjectUtil;
import org.apache.ibatis.builder.annotation.ProviderSqlSource;
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.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.cnbi.project.other.sql.aop.PeriodHolder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** @ClassName ParamInterceptor* @Description 修改接口太繁琐,直接用mybatis拦截器对查询sql进行拦截,将期间参数注入sql* @Author Wangjunkai* @Date 2019/10/23 11:36**/
@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 ParamInterceptor implements Interceptor {private final static Pattern DW_DIMCOMPANY = Pattern.compile("dw_dimcompany", Pattern.CASE_INSENSITIVE);private final static String REPLACE_TXT = "(select * from dw_dimcompany where cisdel = '0' and START_PERIOD <= ? and END_PERIOD > ?)";@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds) args[2];ResultHandler resultHandler = (ResultHandler) args[3];Executor executor = (Executor) invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;if(args.length == 4){boundSql = ms.getBoundSql(parameter);} else {boundSql = (BoundSql) args[5];}//获取sql语句,使用正则忽略大小写匹配String sql = boundSql.getSql();Matcher matcher = DW_DIMCOMPANY.matcher(sql);//没有需要替换的表名则放行if(!matcher.find()){return invocation.proceed();}//收集占位符个数(即paramIndex 的size)以及占位符次序(slot:即参数在ParameterMappings中的顺序)int index = 0;ArrayList<Integer> paramIndex = new ArrayList<>();while(matcher.find(index)){index = matcher.end();String sqlPart = sql.substring(0, index);int slot = index - sqlPart.replace("?", "").length() + paramIndex.size() ;paramIndex.add(slot);paramIndex.add(slot + 1);}//替换子查询String companyPeriodSql = matcher.replaceAll(REPLACE_TXT);cacheKey = args.length == 4 ? executor.createCacheKey(ms, parameter, rowBounds, boundSql) : (CacheKey) args[4];//处理参数Object parameterObject = processParameterObject(ms, parameter, boundSql, cacheKey, paramIndex);BoundSql companyPeriodBoundSql = new BoundSql(ms.getConfiguration(), companyPeriodSql, boundSql.getParameterMappings(), parameterObject);Map<String, Object> additionalParameters = ExecutorUtil.getAdditionalParameter(boundSql);//设置动态参数for (String key : additionalParameters.keySet()) {companyPeriodBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}return executor.query(ms, parameterObject, RowBounds.DEFAULT, resultHandler, cacheKey, companyPeriodBoundSql);}public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey, ArrayList<Integer> paramIndex) {//处理参数Map<String, Object> paramMap = null;if (parameterObject == null) {paramMap = new HashMap<>();} else if (parameterObject instanceof Map) {//解决不可变Map的情况paramMap = new HashMap<>();paramMap.putAll((Map) parameterObject);} else {paramMap = new HashMap<>();// sqlSource为ProviderSqlSource时,处理只有1个参数的情况if (ms.getSqlSource() instanceof ProviderSqlSource) {String[] providerMethodArgumentNames = ExecutorUtil.getProviderMethodArgumentNames((ProviderSqlSource) ms.getSqlSource());if (providerMethodArgumentNames != null && providerMethodArgumentNames.length == 1) {paramMap.put(providerMethodArgumentNames[0], parameterObject);paramMap.put("param1", parameterObject);}}//动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性//TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);//需要针对注解形式的MyProviderSqlSource保存原值if (!hasTypeHandler) {for (String name : metaObject.getGetterNames()) {paramMap.put(name, metaObject.getValue(name));}}//下面这段方法,主要解决一个常见类型的参数时的问题if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {String name = parameterMapping.getProperty();if (!name.equals(GLOBALPERIOD)&& paramMap.get(name) == null) {if (hasTypeHandler|| parameterMapping.getJavaType().equals(parameterObject.getClass())) {paramMap.put(name, parameterObject);break;}}}}}return processPageParameter(ms, paramMap, boundSql, pageKey, paramIndex);}private final static String GLOBALPERIOD = "globalPeriod";public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, BoundSql boundSql, CacheKey pageKey, ArrayList<Integer> paramIndex) {paramMap.put(GLOBALPERIOD, getPeriod());//处理pageKeypageKey.update(getPeriod());//处理参数配置handleParameter(boundSql, ms, paramIndex);return paramMap;}protected void handleParameter(BoundSql boundSql, MappedStatement ms,  ArrayList<Integer> paramIndex) {if (boundSql.getParameterMappings() != null) {List<ParameterMapping> newParameterMappings = new ArrayList<>(boundSql.getParameterMappings());for (Integer index : paramIndex) {if(index < newParameterMappings.size()) {newParameterMappings.add(index, new ParameterMapping.Builder(ms.getConfiguration(), GLOBALPERIOD, String.class).build());}else{newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), GLOBALPERIOD, String.class).build());}}MetaObject metaObject = MetaObjectUtil.forObject(boundSql);metaObject.setValue("parameterMappings", newParameterMappings);}}private final static String Q = "Q";private final static String H = "H";private String getPeriod(){//使用threadlocal保存从request中获取的参数,此处不再描述String period = PeriodHolder.getPeriod();if(NumberUtil.isNumber(period)){return period;}else if(period.contains(Q)){return period.substring(0, 4) + Integer.parseInt(period.substring(5)) * 3;}else if(period.contains(H)){return period.substring(0, 4) + Integer.parseInt(period.substring(5)) * 6;}else{throw new ServiceException("非法期间:" + period);}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {//nothing to do...}
}
2、AutoConfiguration代码实现
package org.cnbi.project.autoconfig;import com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.cnbi.project.other.sql.intercept.ParamInterceptor;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;
import java.util.Iterator;
import java.util.List;/*** @ClassName ParamIntecepterAutoConfiguration* @Description* @Author Wangjunkai* @Date 2019/10/23 15:41**/
@AutoConfigureAfter({MybatisAutoConfiguration.class, PageHelperAutoConfiguration.class})
@Configuration
public class ParamIntecepterAutoConfiguration {@Autowiredprivate List<SqlSessionFactory> sqlSessionFactoryList;public ParamIntecepterAutoConfiguration() {}@PostConstructpublic void addParamInterceptor() {ParamInterceptor interceptor = new ParamInterceptor();Iterator var3 = this.sqlSessionFactoryList.iterator();while(var3.hasNext()) {SqlSessionFactory sqlSessionFactory = (SqlSessionFactory)var3.next();sqlSessionFactory.getConfiguration().addInterceptor(interceptor);}}
}

博主个人微信:

使用mybatis的interceptor修改执行sql以及传入参数相关推荐

  1. [python教程入门学习]python学习笔记(CMD执行文件并传入参数)

    本文章向大家介绍python学习笔记(CMD执行文件并传入参数),主要包括python学习笔记(CMD执行文件并传入参数)使用实例.应用技巧.基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋 ...

  2. mybatis 创建session, 缓存, 执行SQL

    案例代码, 上一个博客已经分析了 SqlSessionFactory 的 build , 本文内容部分知识基于上篇的文章 mybatis 的初始化, build 这节我们分析, 开启session, ...

  3. Entity Framework 在MySQL中执行SQL语句,关于参数问题

    在Entity Framework中添加MySQL模型,在写代码的过程中需要直接执行SQL语句. 在SQL语句中用到了@curRank := 0 这样在SQL语句中定义参数,同时还会有传入参数:ai. ...

  4. sh执行文件 参数传递_sh 脚本执行sql文件传参数

    一.前言 今天做数据删除,用的命令行输入参数,并且调用执行的sql文件,我采用了sed命令,进行替换. sh脚本如下 #! /bin/sh echo "Please enter the ba ...

  5. 字段为NULL导致MyBatis在Oracle上执行SQL报错,无效的列类型

    在oracle中的null为不确定的意思,存储null时,mybatis解析不到oracle字段类型.所以写SQL时最好加上具体的字段类型.如 insert into user(id,name,age ...

  6. MyBatis 源码解读-执行SQL

    Blog blog = mapper.selectBlog(1); 由于所有的Mapper 都是MapperProxy 代理对象,所以任意的方法都是执行MapperProxy 的invoke()方法. ...

  7. sh 脚本执行sql文件传参数

    一.前言 今天做数据删除,用的命令行输入参数,并且调用执行的sql文件,我采用了sed命令,进行替换. sh脚本如下 #! /bin/sh echo "Please enter the ba ...

  8. 一起谈.NET技术,linq2sql:直接执行sql语句

    1.ExecuteQuery方法 看命名,我们很容易联想到ado.net里熟悉的Command的ExecuteNonQuery方法,但是VS的智能提示告诉我们这个方法返回的是一个泛型集合,应该&quo ...

  9. 能不能在FOR循环中执行SQL?

    JDBC最基础的For循环处理SQL的方式 以及执行时间 package javaee.net.cn.jdbc; import java.sql.*; public class TestTransac ...

最新文章

  1. php pdo 判断数据条数据,PDO一次查询一行数据
  2. Microbiome:香港理工李向东组-医院源可吸入耐药基因与宿主群落、临床关联和环境风险...
  3. Java输出数组中最长递增子序列的代码
  4. SQL判断语句用法和多表查询
  5. 最大期望算法与混合高斯模型的推导
  6. es6-promise源码重点分析难点解析
  7. C运算符解析及优先级
  8. 04 canvas——位移画布和旋转缩放
  9. tortoiseGit 解决冲突
  10. Lost and Found(结对项目)功能实现
  11. Oracle从入门到精通
  12. (附源码)计算机毕业设计SSM基于java语言的在线电子书阅读系统
  13. 计算机Word2010中刷新键,Office2010常用快捷键汇总(最新整理)
  14. 用python写冒泡排序_用Python写冒泡排序代码
  15. 人工智能相关书籍介绍
  16. 刨根问底:什么是yum源,yum的工作原理又是什么
  17. java中各种类型所占内存空间大小
  18. 求100以内的所有质数(素数)
  19. 晚上失眠白天没精神,怎么把这种状态调整回来!
  20. 引言-知识技能树(数据分析相关)

热门文章

  1. SuperMap GIS的TIN地形数据处理十问
  2. java calendar计算时间差_Java Calendar 计算时间差
  3. oracle数据库数据合并,Oracle合并数据
  4. pythoniter 2_python [iter(list)] * 2是什么意思?
  5. 优雅代码的秘密,都藏在这6个设计原则中
  6. 看漫画学python下载_漫画批量下载
  7. 刷机升级Android版本,ROM之家简析:Android手机系统怎么升级
  8. 用C语言实现杨辉三角
  9. 协卡助手无法卸载的解决办法
  10. python3.7的IDEL怎么清屏