MyBatis的自定义插件

MyBatis的自定义插件

前置知识

MyBatis 可以拦截的四大组件

  • Executor - 执行器
  • StatementHandler - SQL 语句构造器
  • ParameterHandler - 参数处理器
  • ResultSetHandler - 结果集处理器

自定义 MyBatis 插件

/*** 打印 sql 执行的时间插件*/
@Intercepts(// 指定拦截器拦截的对象、方法和参数类型{@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})}
)
// 注册到 Spring 容器,不是 Spring 环境的话可以用 mybatis 的 config 配置进去
@Component
public class SqlExecuteTimePrintMybatisPlugin implements Interceptor {protected Logger logger = LoggerFactory.getLogger(SqlExecuteTimePrintMybatisPlugin.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 获取代理对象StatementHandler statementHandler = (StatementHandler) invocation.getTarget();// 获取执行 sqlBoundSql boundSql = statementHandler.getBoundSql();// 此处简单处理一下,只打印参数替换前的 sql,目的是演示自定义插件String sql = boundSql.getSql();long start = System.currentTimeMillis();try {return invocation.proceed();} finally {logger.info("sql -> {}, takes time -> {}", sql, System.currentTimeMillis() - start);}}
}

效果如下

2023-10-14 17:18:39.297  INFO 25972 --- [p-nio-80-exec-1] c.y.m.c.SqlExecuteTimePrintMybatisPlugin : sql -> SELECT * FROM `INFO` WHERE `id` = ? , takes time -> 57
2023-10-14 17:18:39.324  INFO 25972 --- [p-nio-80-exec-1] c.y.m.c.SqlExecuteTimePrintMybatisPlugin : sql -> SELECT `id`, `info_id`, `extend_info` FROM `INFO_DETAIL` WHERE `info_id` = ?, takes time -> 4

源码解析

创建四大对象的代码如下

public class 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;}
}
  1. 首先在创建 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四个对象时,将插件(plugins)注入

  2. 调用 InterceptorChain.pluginAll() 方法将插件增加到责任链,并返回代理后的 target 包装对象,InterceptorChain 保存了所有的拦截器(Interceptors)

  3. 最终在执行的时候调用的其实是 JDK 动态代理的对象,执行 MyBatis 中 InvocationHandler 的实现 org.apache.ibatis.plugin.Plugin 的 invoke 方法

public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}
}
public interface Interceptor {// 拦截器增强方法Object intercept(Invocation invocation) throws Throwable;// 包装原来的对象default Object plugin(Object target) {return Plugin.wrap(target, this);}default void setProperties(Properties properties) {// NOP}}
public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;private final Map<Class<?>, Set<Method>> signatureMap;private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {// jdk 动态代理return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 获取插件中生命要增强的方法Set<Method> methods = signatureMap.get(method.getDeclaringClass());// 如果命中该方法,就使用执行插件中增强的方法if (methods != null && methods.contains(method)) {return interceptor.intercept(new Invocation(target, method, args));}// 没有命中就不对方法进行增强return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}...
}

备注

不明白的需要去看下 JDK 动态代理实现原理,概括的来讲就是 Proxy#newProxyInstance 时,通过字节码增强的方法,生成一个实现了跟被代理类相同接口并继承了 java.lang.reflect.Proxy 的类并返回其实例,调用这个代理类的方法时,实际上调用的是 Proxy.InvocationHandler.invoke(this, method, new Object[]{args}) 方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/105285.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

课程31:API请求日志脱敏处理

这里写目录标题 🚀前言一、日志脱敏处理介绍二、配置脱敏2.1 登录2.2 新增用户2.3 修改用户信息2.4 修改用户密码三、修改API日志记录3.1 修改参数3.2 修改Get提交的参数3.3 进行脱敏处理3.3.1 脱敏处理3.3.2 GetParameterValue方法3.4 请求日志3.4.1 权限3.4.2 参数过滤器3.…

电子电器架构——基于Adaptive AUTOSAR的电子电器架构简析

基于Adaptive AUTOSAR的电子电器架构简析 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…

51单片机之串口通信例程

51单片机之串口通信例程 简介原理例程 简介 串行通信是指使用一条数据线&#xff0c;将数据一位一位地依次传输&#xff0c;每一位数据占据一个固定的时间长度。在串行通信中&#xff0c;数据可以以字符为单位进行传输&#xff0c;也可以以帧为单位进行传输。 在51单片机中&a…

如何在.NET Core3.1 类库项目中使用System.Windows.Forms

网上说法大多都是直接添加对.Net Framework框架的引用&#xff0c;但是这种方法打包很不友好。于是开始了网络搜索&#xff0c;翻到了微软的文档&#xff0c;才找到直接引用 System.Windows.Froms 程序集的方法。还隐藏的很深&#xff0c;地址&#xff1a;Upgrade a Windows Fo…

js 删除数组中指定元素——5种方式

1.使用splice()方法删除元素 JavaScript 中的 splice() 方法可用于在数组中添加或删除元素。如果我们需要删除数组中的元素&#xff0c;可以使用 splice() 方法。该方法接受两个参数&#xff0c;第一个参数指定要删除的元素的位置&#xff0c;第二个参数指定要删除的元素个数&…

【数据结构】:二叉树与堆排序的实现

1.树概念及结构(了解) 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的有一个特殊的结点&#…

vuedraggable遇到的问题

一、拖拽无效&#xff1a;触发start事件&#xff0c;但不执行end方法 原因&#xff1a;外层的父组件使用了vue-draggable-resizable组件封装的可拖拽的弹框组件 二、拖拽后位置未发生变化&#xff1a;start、end事件都执行 原因1、draggble绑定的v-mode和下方列表不一致 原…

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆 前言一. 大小根堆二. 数据流的中位数1.1 初始化1.2 插入操作1.3 完整代码 三. 滑动窗口中位数3.1 在第一题的基础上改造3.2 栈的remove操作 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 大小根堆 先来说下大小根堆是什…

PHP筆記

​ 前言因緣際會下還是開始學習php了。經歷了風風雨雨終於在今年暑假要去加拿大留學了&#xff0c;php會是第二年的其中一門必修課程&#xff0c;加上最近前端也真的蠻心累&#xff0c;也許有一門精進的後端語言&#xff0c;日後轉職會有更寬廣的道路&#xff0c;對自己說加油&…

Qt 布局(QLayout 类QStackedWidget 类) 总结

一、QLayout类(基本布局) QLayout类是Qt框架中用于管理和排列QWidget控件的布局类。它提供了一种方便而灵活的方式来自动布局QWidget控件。QLayout类允许您以一种简单的方式指定如何安排控件&#xff0c;并能够自动处理控件的位置和大小&#xff0c;以使其适应更改的父窗口的大…

竞赛选题 深度学习OCR中文识别 - opencv python

文章目录 0 前言1 课题背景2 实现效果3 文本区域检测网络-CTPN4 文本识别网络-CRNN5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习OCR中文识别系统 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;…

【LeetCode刷题(数据结构)】:另一颗树的子树

给你两棵二叉树 root 和 subRoot 检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子…

数据库安全-H2 databaseElasticsearchCouchDBInfluxdb漏洞复现

目录 数据库安全-H2 database&Elasticsearch&CouchDB&Influxdb 复现influxdb-未授权访问-jwt 验证H2database-未授权访问-配置不当CouchDB-权限绕过配合 RCE-漏洞CouchDB 垂直权限绕过Couchdb 任意命令执行 RCE ElasticSearch-文件写入&RCE-漏洞Elasticsearch写…

CentOS 挂载新磁盘以及磁盘扩容操作教程

1.搭载新加磁盘 # 查看磁盘 fdisk -l #新盘&#xff08;/dev/sdb&#xff09;创建分区 #虚拟机 fdisk /dev/sdb #阿里云 fdisk /dev/vdb #创建/dev/sdb1为新的PV&#xff08;物理卷&#xff09; 【创建物理卷命令】 #虚拟机 pvcreate /dev/sdb1 #阿里云 pvcreate /dev/vdb1 查…

c 语言基础题目:L1-037 A除以B

真的是简单题哈 —— 给定两个绝对值不超过100的整数A和B&#xff0c;要求你按照“A/B商”的格式输出结果。 输入格式&#xff1a; 输入在第一行给出两个整数A和B&#xff08;−100≤A,B≤100&#xff09;&#xff0c;数字间以空格分隔。 输出格式&#xff1a; 在一行中输出结…

【C语言】通讯录的简单实现

通讯录的内容 contect.h #pragma once // 包含头文件 #include <stdio.h> #include <string.h> #include <assert.h> #include <stdlib.h>// 使用枚举常量定义功能 enum Function {quit, // 注意这是逗号&#xff0c;不是分号save,addition,delete,s…

27 mysql 组合索引 的存储以及使用

前言 这里来看一下 mysql 中索引的 增删改查 查询在前面的系列文章中都有使用到 这里 来看一下 增删改 的相关实现 索引记录 和 数据记录 的处理方式是一致的 这里来看一下 组合索引 的相关, 以及 特性 组合索引的存储以及使用 创建数据表如下, 除了主键之外, 创建了…

辅助驾驶功能开发-功能对标篇(3)-NOP领航辅助系统-蔚来

1.横向对标参数 厂商蔚来车型ES6/8ET7ET5ES7上市时间201920222022Q32022Q2方案7v5R+1DMS11v5R1L+1DMS11v5R1L+1DMS11v5R1L+1DMS摄像头前视摄像头1*(三目)2*(8M,长短焦距)2*(8M,长短焦距)2*(8M,长短焦距)侧视摄像头/4*(8M)4*(8M)4*(8M)后视摄像头/1*(8M)1*(8M)1*(8M)环视摄像头4…

Spring Boot 生成二维码

效果图 1.maven依赖 <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId>

js的入口函数

JavaScript的入口函数是指在HTML页面加载完毕后&#xff0c;JavaScript代码开始执行的函数。这个函数一般被称为onload函数&#xff0c;它的作用是在页面加载完成后执行一些初始化操作&#xff0c;或者对页面进行动态修改。 在HTML页面中&#xff0c;可以通过以下方式设置入口…