mybatis自定义插件(拦截器)开发详解
mybatis插件(准确的说应该是around拦截器,因为接口名是interceptor,而且invocation.proceed要自己调用,配置中叫插件)功能非常强大,可以让我们无侵入式的对SQL的执行进行干涉,从SQL语句重写、参数注入、结果集返回等每个主要环节,典型的包括权限控制检查与注入、只读库映射、K/V翻译、动态改写SQL。
MyBatis默认支持对4大对象(Executor,StatementHandler,ParameterHandler,ResultSetHandler)上的方法执行拦截,具体支持的方法为:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed),主要用于sql重写。
- ParameterHandler (getParameterObject, setParameters),用于参数处理。
- ResultSetHandler (handleResultSets, handleOutputParameters),用于结果集二次处理。
- StatementHandler (prepare, parameterize, batch, update, query),用于jdbc层的控制。
大多数情况下仅在Executor做插件比如SQL重写、结果集脱敏,ResultSetHandler和StatementHandler仅在高级场景中使用,而且某些场景中非常有价值。
四大对象的在sql执行过程中的调用链如下:

具体的方法定义可以参见每个类方法的签名,这里就不详细展开了。这四个类被创建后不是直接返回,而是创执行了interceptorChain.pluginAll(parameterHandler)才返回。如下所示:
//Configuration 中
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
编写Interceptor的实现类
Executor拦截器
@Intercepts({
// @Signature(type = Executor.class, method = /* org.apache.ibatis.executor.Executor中定义的方法,参数也要对应 */"update", args = { MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class }) })
public class SelectPruningColumnPlugin implements Interceptor {
public static final ThreadLocal<ColumnPruning> enablePruning = new ThreadLocal<ColumnPruning>(){
@Override
protected ColumnPruning initialValue()
{
return null;
}
};
Logger logger = LoggerFactory.getLogger(SelectPruningColumnPlugin.class);
static int MAPPED_STATEMENT_INDEX = 0;// 这是对应上面的args的序号
static int PARAMETER_INDEX = 1;
static int ROWBOUNDS_INDEX = 2;
static int RESULT_HANDLER_INDEX = 3;
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (enablePruning.get() != null && enablePruning.get().isEnablePruning()) {
Object[] queryArgs = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];
Object parameter = queryArgs[PARAMETER_INDEX];
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
String sql = boundSql.getSql();// 获取到SQL ,进行调整
String name = mappedStatement.getId();
logger.debug("拦截的方法名是:" + name + ",sql是" + sql + ",参数是" + JsonUtils.toJson(parameter));
String execSql = pruningColumn(enablePruning.get().getReserveColumns(), sql);
logger.debug("修改后的sql是:" + execSql);
// 重新new一个查询语句对像
BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), execSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
// 把新的查询放到statement里
MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
for (ParameterMapping mapping : boundSql.getParameterMappings()) {
String prop = mapping.getProperty();
if (boundSql.hasAdditionalParameter(prop)) {
newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
}
}
queryArgs[MAPPED_STATEMENT_INDEX] = newMs;
// 因为涉及分页查询PageHelper插件,所以不能设置为null,需要业务上下文执行完成后设置为null
// enablePruning.set(null);
}
Object result = invocation.proceed();
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
ResultSetHandler拦截器
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class}) })
public class OptMapPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
Statement stmt = (Statement) invocation.getArgs()[0];
if (target instanceof DefaultResultSetHandler) {
DefaultResultSetHandler resultSetHandler = (DefaultResultSetHandler) target;
Class clz = resultSetHandler.getMappedStatement().getResultMaps().get(0).getType();
if (clz == OptMap.class) {
List<Object> resultList = new ArrayList<Object>();
OptMap optMap = new OptMap();
resultList.add(optMap);
resultSet2OptMap(resultSetHandler.getConfiguration(),resultSetHandler,optMap,stmt.getResultSet());
return resultList;
}
return invocation.proceed();
}
//如果没有进行拦截处理,则执行默认逻辑
return invocation.proceed();
}
最后将插件配置到mybatis-config.xml中,如下:
<!-- mybatis-config.xml 注册插件-->
<plugins>
<plugin interceptor="io.yidoo.mybatis.plugin.SelectPruningColumnPlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
mybatis自定义插件(拦截器)开发详解的更多相关文章
- Mybatis自定义SQL拦截器
本博客介绍的是继承Mybatis提供的Interface接口,自定义拦截器,然后将项目中的sql拦截一下,打印到控制台. 先自定义一个拦截器 package com.muses.taoshop.com ...
- springboot拦截器HandlerInterceptor详解
Web开发中,我们除了使用 Filter 来过滤请web求外,还可以使用Spring提供的HandlerInterceptor(拦截器). HandlerInterceptor 的功能跟过滤器类似,但 ...
- 拦截器 应用详解--SpringMVC
在实际项目中,拦截器的使用是非常普遍的,例如在购物网站中通过拦截器可以拦截未登录的用户,禁止其购买商品,或者使用它来验证已登录用户是否有相应的操作权限等,Spring MVC提供了拦截器功能,通过配置 ...
- mybatis Interceptor拦截器代码详解
mybatis官方定义:MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis ...
- Flume 拦截器(interceptor)详解
flume 拦截器(interceptor)1.flume拦截器介绍拦截器是简单的插件式组件,设置在source和channel之间.source接收到的事件event,在写入channel之前,拦截 ...
- mybatis自定义分页拦截器
最近看了一下项目中代码,发现系统中使用的mybatis分页使用的是mybatis自带的分页,即使用RowBounds来进行分页,而这种分页是基于内存分页,即一次查出所有的数据,然后再返回分页需要的数据 ...
- Spring 注解拦截器使用详解
Spring mvc拦截器 平时用到的拦截器通常都是xml的配置方式.今天就特地研究了一下注解方式的拦截器. 配置Spring环境这里就不做详细介绍.本文主要介绍在Spring下,基于注解方式的拦截器 ...
- axios 源码解析(下) 拦截器的详解
axios的除了初始化配置外,其它有用的应该就是拦截器了,拦截器分为请求拦截器和响应拦截器两种: 请求拦截器 ;在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要 ...
- javaCV开发详解之4:转流器实现(也可作为本地收流器、推流器,新增添加图片及文字水印,视频图像帧保存),实现rtsp/rtmp/本地文件转发到rtmp流媒体服务器(基于javaCV-FFMPEG)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
- javaCV开发详解之3:收流器实现,录制流媒体服务器的rtsp/rtmp视频文件(基于javaCV-FFMPEG)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
随机推荐
- 非root安装rpm时,mockbuild does not exist - using root
1.现象 [fedora@k8s-cluster--ycmwlao4q5wz-minion- ~]$ [fedora@k8s-cluster--ycmwlao4q5wz-minion- ~]$ sud ...
- 在页面获取本地电脑IP
<%@ page language="java" import="java.util.*" contentType="text/html; ch ...
- VS code key shortcuts for windows
mac上的快捷键,尽量是选择像我用vs studio上靠近. ctrl+K+S: 显示快捷键列 ctrl+shift+p: 系统配置命令行 ctrl+p:项目中文件列表,选择文件 Alt+M:当前文件 ...
- 解决vant-weapp组件库的example的导入问题
最近在学习小程序,看到了vant-weapp这个组件库,我比较喜欢边看示例边来敲代码.刚好这个组件库下载下来有 example的文件夹.废话不多说,现在来看看怎么在开发工具里面导入吧! 步骤: 1.下 ...
- Odoo XML中操作记录与函数
转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10826037.html 一:XML文件中定义记录 XML中定义记录: 每个<record>元素有 ...
- 浅谈flask源码之请求过程
更新时间:2018年07月26日 09:51:36 作者:Dear. 我要评论 这篇文章主要介绍了浅谈flask源码之请求过程,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随 ...
- kuangbin专题专题四 Til the Cows Come Home POJ - 2387
题目链接:https://vjudge.net/problem/POJ-2387 题意:从编号为n的城市到编号为1的城市的最短路. 思路:dijkstra模板题,直接套板子,代码中我会带点注释给初学者 ...
- 上传自己的构件(Jar)到Maven中央仓库
背景: 用了Maven之后,你有没有这样的想法,自己一直在使用别人贡献的代码,自己能不能把自己觉得好的代码也贡献出来让大家方便. 还有如果你也是一名程序员,你会不会觉得要是把自己积累起来日常常用的代码 ...
- web api .net C# mvc API返回XML文档的解析并取值
[HttpGet] public System.Net.Http.HttpResponseMessage GetNotify() { var xmlstring = @" <xml&g ...
- Django如何渲染markdown
本文已默认你已经好创建Django工程和App. 依赖包 pip install markdown django-markup bleach bleach-whitelist 示例代码 your_ap ...