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 ...
随机推荐
- Android-----使用SoapObject获取服务器数据
新建两个工具类ConnectWeb.java 和 ConnectMethod.java 进行对服务器进行数据交互 ConnectWeb.java代码如下: public class ConnectWe ...
- 1 Dalvik和Java虚拟机
Dalvik虚拟机特点: 1. 体积小 2. DEX格式可执行文件,相比java运行速度快 3. 常量池采用32位索引值 4. 提供对象生命周期,堆栈,线程,权限,异常等管理 5. Andr ...
- postgresql源代码结构
转载学习: 德哥培训! 源码下载: https://www.postgresql.org/ftp/source/ 1.postgressql源码目录结构 2.src目录结构
- [SourceInsight].source insight 使用技巧
转自:https://www.veryarm.com/140428.html 1 开胃菜-初级应用 1.1 选择美丽的界面享受工作 虽然不能以貌取人,但似乎从来没有人责备以貌取软件的.SI的华丽界 ...
- Vue基本用法
在学习Vue的基本用法之前,我们先简单的了解一些es6的语法 let: 特点:1.局部作用域 2.不会存在变量提升 3.变量不能重复声明 const: 特点:1.局部作用域 2.不会存在变量提升 3. ...
- nginx 负载均衡,如何判断某台服务器宕机?
答:使用参数:proxy_connect_timeout: 这个参数是连接的超时时间.设置成1,表示是1秒后超时会连接到另外一台服务器. 配置在 server 部分里.
- 牛客NOIP暑期七天营-提高组2C:滑块(平衡树) (这里rope骗分)
A:hash 或者 map 或者trie. #include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) usin ...
- python基础语法3 元组,字典,集合
元组: ========================元组基本方法===========================用途:存储多个不同类型的值定义方式:用过小括号存储数据,数据与数据之间通过逗号 ...
- python面试题&练习题之函数
1.写函数,接收两个数字参数,返回最大值例如:传入:10,20返回:20 def res_max(number1,number2): l1 = [] l1.append(number1) l1.app ...
- Content-Type与MIME
http://www.cnblogs.com/jsean/articles/1610265.html 首先,我们要了解浏览器是如何处理内容的.在浏览器中显示的内容有 HTML.有 XML.有 GIF. ...