mybatis拦截器
mybatis拦截器
什么是Mybatis插件
与其称为Mybatis插件,不如叫Mybatis拦截器,更加符合其功能定位,实际上它就是一个拦截器,应用代理模式,在方法级别上进行拦截。
支持拦截的方法
执行器Executor(update、query、commit、rollback等方法);
参数处理器ParameterHandler(getParameterObject、setParameters方法);
结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法);
Mybatis 通过插件(Interceptor) 可以做到拦截四大对象相关方法的执行,根据需求,完
成相关数据的动态改变。
创建StatementHandler的时候 会自动创建ParameterHandler和ResultSetHandler
Executor代理对象
ParameterHandler代理对象
ResultSetHandler代理对象
StatementHandler代理对象
观察源码,发现这些可拦截的类对应的对象生成都是通过InterceptorChain的pluginAll方法来创建的,进一步观察pluginAll方法,如下:
遍历所有拦截器,调用拦截器的plugin方法生成代理对象,注意生成代理对象重新赋值给target,
所以如果有多个拦截器的话,生成的代理对象会被另一个代理对象代理,从而形成一个代理链条,执行的时候,依次执行所有拦截器的拦截逻辑代码;
插件原理
四大对象的每个对象在创建时,都会执行 interceptorChain.pluginAll(),会经过每个插
件对象的 plugin()方法,目的是为当前的四大对象创建代理。代理对象就可以拦截到四
大对象相关方法的执行,因为要执行四大对象的方法需要经过代理.
mybatis拦截器应用
最近项目中需要增加「数据权限」功能。所谓的「数据权限」是指不同用户在查询某张表的数据时看到的数据范围是不一样的,
有的用户可以看到全部门店的数据,有的用户只能看到部分门店的数据,这就需要给不同角色用户配置不同的数据范围查看权限。
比如 “商品信息表” 中有所属门店的 “门店id” 字段,用户能看到哪些门店的商品信息数据是根据配置的数据权限来判断的,
例如A 用户能看到 3 家门店的商品信息数据,B 用户能看到 4 家门店的商品信息数据。
具体的实现就是在对应的 SQL 查询语句的 WHERE 条件中加上 tableName.shopId IN (shopId1, shopId2,.....) 这个条件即可。
(PS:系统中的十几张表都有 “门店id” 字段,所以对应的就有十几个功能需要加上数据权限)
在日常开发业务场景中,经常要遇到对 实体进行通用性操作,如:
进行持久化时,出现的保存 创建人信息
更新数据时候,通用更新 操作人信息
指定接口自定义主键,等
为了解决这类问题,可以采用AOP 也可以采用拦截器,以下是采用MyBatis拦截器进行实现的
MyBatis的拦截器采用责任链设计模式,多个拦截器之间的责任链是通过动态代理组织的。
我们一般都会在拦截器中的intercept方法中往往会有invocation.proceed()语句,其作用是将拦截器责任链向后传递,本质上便是动态代理的invoke。
比如现在我要实现一个功能 对操作中的字段自动添加操作日期
自定义mybatis拦截器 需要实现Interceptor接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class MybatisInterceptor implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { } public Object plugin(Object target) { // return target instanceof Executor ? Plugin.wrap(target, this) : target; // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数 return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this ) : target; } public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target; } |
首先需要前签名 也就是我们这个拦截器对象需要拦截哪些对象 指定拦截哪些方法 指定方法的参数
也就是说我们可以在plugin里面利用动态代理来返回我们这个插件具体想要调用的代理对象比如是statementHandler还是executor
因为这四个代理对象都会调用plugin方法 然后我们既然使用了动态代理 那代理对象去执行plugin方法的时候
就会自动加载我们的拦截器对象 然后就会执行我们拦截器对象的interceptor里面去处理我们需要的方法 我们需要在intercept接口中写我们的业务
初版 以后还需要修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | package com.po.pf.interceptor; import com.po.pf.domain.User; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.plugin.*; import java.sql.Connection; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** * @author 破 * @function * @date 2020/5/7 18:37 */ @Intercepts ({ @Signature ( type = StatementHandler. class , method = "prepare" , args = {Connection. class , Integer. class } ) }) /*@Intercepts({@Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class })})*/ public class MybatisInterceptor implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { if (invocation.getTarget() instanceof StatementHandler) { StatementHandler delegate = (StatementHandler)invocation.getTarget(); BoundSql boundSql = delegate.getBoundSql(); Object obj = boundSql.getParameterObject(); MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement" ); //拦截到的prepare方法参数是一个Connection对象 AtomicReference<String> sql = new AtomicReference<>(boundSql.getSql()); Connection connection = (Connection)invocation.getArgs()[ 0 ]; System.out.println( "成功拦截sql::::" +sql); //获取参数 Map parameterObject = (Map)boundSql.getParameterObject(); System.out.println( "parameterObject:" +parameterObject); Set set = parameterObject.keySet(); set.forEach(s->{ System.out.println( "!!!!!!!!!!!!!" +parameterObject.get(s)); if (parameterObject.get(s) instanceof User){ User user=(User)parameterObject.get(s); //处理业务 //sql.set(sql + " where user_id=" + user.getId()); } }); ReflectUtil.setFieldValue(boundSql, "sql" , sql + " where user_id=1" ); } return invocation.proceed(); } public Object plugin(Object target) { // return target instanceof Executor ? Plugin.wrap(target, this) : target; // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数 // return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target; //return Plugin.wrap(target, this); if (target instanceof StatementHandler) { return Plugin.wrap(target, this ); } else { return target; } } public void setProperties(Properties properties) { } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY