mybatis 插件开发
基于上面这三步,大家先看一下我们这插件怎么写,以及这个插件的效果。
先说明一下本文涉及到的源码 mybatis 版本是 3.4.0。
本文用拦截器的目的是判断 delete 语句中是否有 where 条件。所以,开发出来的插件长这样:
再来一个复制粘贴直接运行版本:
@Slf4j @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), }) public class CheckSQLInterceptor implements Interceptor { private static String SQL_WHERE = "where"; @Override public Object intercept(Invocation invocation) throws Throwable { //获取方法的第0个参数,也就是MappedStatement。@Signature注解中的args中的顺序 MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; //获取sql命令操作类型 SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); final Object[] queryArgs = invocation.getArgs(); final Object parameter = queryArgs[1]; BoundSql boundSql = mappedStatement.getBoundSql(parameter); String sql = boundSql.getSql(); if (SqlCommandType.DELETE.equals(sqlCommandType)) { //格式化sql sql = sql.replace("\n", ""); if (!sql.toLowerCase().contains(SQL_WHERE)) { sql = sql.replace(" ", ""); log.info("删除语句中没有where条件,sql为:{}", sql); throw new Exception("删除语句中没有where条件"); } } return invocation.proceed(); } @Override public Object plugin(Object o) { return Plugin.wrap(o, this); } @Override public void setProperties(Properties properties) { } }
再把插件注册上(注册插件还有其他的方法,后面会讲到,这里只是展示Bean注入的方式):
我们先看看配上插件后的执行效果:
可以看到日志中输出了:
删除语句中没有where条件,sql为:delete from order_info_ext
并抛出了异常。
这样,我们的扩展表的数据就保住了。在测试阶段,测试同学就一定能扯出来问题,瞟一眼日志就明白了。
就算测试同学忘记测试了,在生产上也不会执行成功,抛出异常后还会有报警短信通知到相应的开发负责人,及时登上服务器去处理。
功能实现了,确实是非常的简单。
我们再说回代码,你说说看:当你拿到上面这段代码后,最迷惑的地方是哪里?
其中的逻辑是很简单的了。 没有什么特别的地方,我想大多数人拿到这段代码迷惑的地方在于这个地方吧:
这个 @Intercepts 里面的 @Signature 里面为什么要这样配置?
我们先看看 @Intercepts 注解:
里面是个数组,可以配置多个 Signature。所以,其实这样配置也是可以的:
关键的地方在于 @Signature 怎么配置:
这个问题,我们放到下一节去讨论。
mybatis插件的原理
上面一小节我们知道了对于开发插件而言,难点在于 @Signature 怎么配置。
其实这也不能叫难点,只能说你不知道能配置什么,比较茫然而已。这一小节就来回答这个问题。
要知道怎么配置就必须要了解mybatis 这四大对象:Executor、ParameterHandler 、ResultSetHandler 、StatementHandler 。
官网上说:
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
那官网上说的这四大对象分别是拿来干啥用的呢?
Executor:Mybatis 的执行器,用于进行增删改查的操作。
ParameterHandler :参数处理器,用于处理 SQL 语句中的参数对象。
ResultSetHandler:结果处理器,用于处理 SQL 语句的返回结果。
StatementHandler :数据库的处理对象,用于执行SQL语句
知道拦截的四大对象了,我们就可以先揭秘一下上面的这个注解配置的是啥了:
type 字段存放的是 class 对象,其取值范围就是上面说的四大对象。
method 字段存放的是 class 对象的具体方法。
args 存放的是具体方法的参数。
看到这几个参数你想到了什么?有没有条件反射式的想到反射?如果没有的话你再咂摸咂摸,看看能不能品出一点反射的味道。
本文用拦截器的目的是判断 delete 语句中是否有 where 条件,因此经过上面的分析,Executor 对象就能满足我们的需求。
所以在本文示例中 @Signature 的 type 字段就是 Executor.class。
那 method 字段我们放哪个方法呢?放 delete 吗?
这就得看看 Executor 对象的方法有哪些:
可以看到其中并没有 delete 方法,和 SQL 执行相关的,看起来只有 query和 update。
但是,我们可以大胆猜测一下呀:delete 也是一种 update。
接着去求证一下就行:
可以看到 delete 方法确实是调用了 update 方法。
所以在本文案例中 @Signature 的 method 字段放的是 update 方法。
已经知道具体的方法了,那 args 放的就是方法的入参,所以这段配置就是这样来的:
真的,我觉得这属于手摸手教学系列了。经过这个简单的案例,我希望大家能做到一通百通。
接下来带大家看看我们常用的分页插件 pageHelper 是怎么做的吧。
其实你用脚指头也能想到,分页插件肯定是拦截的查询方法,我们只是需要去验证一下就可以。
引入 pageHelper 后可以看到 Interceptor 的多了两个实现:
我们看一下 PageInterceptor 方法吧:
对吧,拦截了两个 query 方法,一个参数是 4 个,一个参数是 6 个:
同时,在 intercept 的实现里面有一部分是这样写的:
4 个参数和 6 个参数是做了单独处理的,至于为什么要这样处理,至于为什么要拦截两个 query 方法,说起来又是一个很长的故事了。
详细的可以看看这个链接: https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Interceptor.md
好了,还是那句话:如果要写出好的 mybatis 插件,必须知道 @Signature 怎么去配置。配置后能拦截哪些东西,你心里应该是有点数的。