Mybatis Interceptor * 的实现
作者:广训 发布时间:2022-11-02 05:05:44
Mybatis采用责任链模式,通过 * 组织多个 * (插件),通过这些 * 可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。
* (Interceptor)在 Mybatis 中被当做插件(plugin)对待,官方文档提供了 Executor,ParameterHandler,ResultSetHandler,StatementHandler 共4种,并且提示“这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码”。
* 的使用场景主要是更新数据库的通用字段,分库分表,加解密等的处理。
1. Interceptor
* 均需要实现该 org.apache.ibatis.plugin.Interceptor
接口。
2. Intercepts *
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
* 的使用需要查看每一个type所提供的方法参数。
Signature 对应 Invocation 构造器,type 为 Invocation.Object,method 为 Invocation.Method,args 为 Invocation.Object[]。
method 对应的 update 包括了最常用的 insert/update/delete 三种操作,因此 update 本身无法直接判断sql为何种执行过程。
args 包含了其余所有的操作信息, 按数组进行存储, 不同的拦截方式有不同的参数顺序, 具体看type接口的方法签名, 然后根据签名解析。
3. Object 对象类型
args 参数列表中,Object.class 是特殊的对象类型。如果有数据库统一的实体 Entity 类,即包含表公共字段,比如创建、更新操作对象和时间的基类等,在编写代码时尽量依据该对象来操作,会简单很多。该对象的判断使用
Object parameter = invocation.getArgs()[1];
if (parameter instanceof BaseEntity) {
BaseEntity entity = (BaseEntity) parameter;
}
即可,根据语句执行类型选择对应字段的赋值。
如果参数不是实体,而且具体的参数,那么 Mybatis 也做了一些处理,比如 @Param("name") String name 类型的参数,会被包装成 Map 接口的实现来处理,即使是原始的 Map 也是如此。使用
Object parameter = invocation.getArgs()[1];
if (parameter instanceof Map) {
Map map = (Map) parameter;
}
即可,对具体统一的参数进行赋值。
4. SqlCommandType 命令类型
Executor
提供的方法中,update
包含了 新增,修改和删除类型,无法直接区分,需要借助 MappedStatement
类的属性 SqlCommandType
来进行判断,该类包含了所有的操作类型
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
毕竟新增和修改的场景,有些参数是有区别的,比如创建时间和更新时间,update 时是无需兼顾创建时间字段的。
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
SqlCommandType commandType = ms.getSqlCommandType();
5. 实例
自己编写的小项目中,需要统一给数据库字段属性赋值:
public class BaseEntity {
private int id;
private int creator;
private int updater;
private Long createTime;
private Long updateTime;
}
dao 操作使用了实体和参数的方式,个人建议还是统一使用实体比较简单,易读。
使用实体:
int add(BookEntity entity);
使用参数:
int update(@Param("id") int id, @Param("url") String url, @Param("description") String description, @Param("playCount") int playCount, @Param("creator") int creator, @Param("updateTime") long updateTime);
完整的例子:
package com.github.zhgxun.talk.common.plugin;
import com.github.zhgxun.talk.common.util.DateUtil;
import com.github.zhgxun.talk.common.util.UserUtil;
import com.github.zhgxun.talk.entity.BaseEntity;
import com.github.zhgxun.talk.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Properties;
/**
* 全局拦截数据库创建和更新
* <p>
* Signature 对应 Invocation 构造器, type 为 Invocation.Object, method 为 Invocation.Method, args 为 Invocation.Object[]
* method 对应的 update 包括了最常用的 insert/update/delete 三种操作, 因此 update 本身无法直接判断sql为何种执行过程
* args 包含了其余多有的操作信息, 按数组进行存储, 不同的拦截方式有不同的参数顺序, 具体看type接口的方法签名, 然后根据签名解析, 参见官网
*
* @link http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins 插件
* <p>
* MappedStatement 包括了SQL具体操作类型, 需要通过该类型判断当前sql执行过程
*/
@Component
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
public class NormalPlugin implements Interceptor {
@Override
@SuppressWarnings("unchecked")
public Object intercept(Invocation invocation) throws Throwable {
// 根据签名指定的args顺序获取具体的实现类
// 1. 获取MappedStatement实例, 并获取当前SQL命令类型
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
SqlCommandType commandType = ms.getSqlCommandType();
// 2. 获取当前正在 * 作的类, 有可能是Java Bean, 也可能是普通的操作对象, 比如普通的参数传递
// 普通参数, 即是 @Param 包装或者原始 Map 对象, 普通参数会被 Mybatis 包装成 Map 对象
// 即是 org.apache.ibatis.binding.MapperMethod$ParamMap
Object parameter = invocation.getArgs()[1];
// 获取 * 指定的方法类型, 通常需要拦截 update
String methodName = invocation.getMethod().getName();
log.info("NormalPlugin, methodName; {}, commandType: {}", methodName, commandType);
// 3. 获取当前用户信息
UserEntity userEntity = UserUtil.getCurrentUser();
// 默认测试参数值
int creator = 2, updater = 3;
if (parameter instanceof BaseEntity) {
// 4. 实体类
BaseEntity entity = (BaseEntity) parameter;
if (userEntity != null) {
creator = entity.getCreator();
updater = entity.getUpdater();
}
if (methodName.equals("update")) {
if (commandType.equals(SqlCommandType.INSERT)) {
entity.setCreator(creator);
entity.setUpdater(updater);
entity.setCreateTime(DateUtil.getTimeStamp());
entity.setUpdateTime(DateUtil.getTimeStamp());
} else if (commandType.equals(SqlCommandType.UPDATE)) {
entity.setUpdater(updater);
entity.setUpdateTime(DateUtil.getTimeStamp());
}
}
} else if (parameter instanceof Map) {
// 5. @Param 等包装类
// 更新时指定某些字段的最新数据值
if (commandType.equals(SqlCommandType.UPDATE)) {
// 遍历参数类型, 检查目标参数值是否存在对象中, 该方式需要应用编写有一些统一的规范
// 否则均统一为实体对象, 就免去该重复操作
Map map = (Map) parameter;
if (map.containsKey("creator")) {
map.put("creator", creator);
}
if (map.containsKey("updateTime")) {
map.put("updateTime", DateUtil.getTimeStamp());
}
}
}
// 6. 均不是需要被拦截的类型, 不做操作
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
6. 感受
其它几种类型,后面在看,尤其是分页。
在编写代码的过程中,有时候还是需要先知道对象的类型,比如 Object.class 跟 Interface 一样,很多时候仅仅代表一种数据类型,需要明确知道后再进行操作。
@Param 标识的参数其实会被 Mybatis 封装成 org.apache.ibatis.binding.MapperMethod$ParamMap 类型,一开始我就是使用
for (Field field:parameter.getClass().getDeclaredFields()) {
}
的方式获取,以为这些都是对象的属性,通过反射获取并修改即可,实际上发现对象不存在这些属性,但是打印出的信息中,明确有这些字段的,网上参考一些信息后,才知道这是一个 Map 类型,问题才迎刃而解。
来源:https://segmentfault.com/a/1190000017393523
猜你喜欢
- 在application.properties中配置了static的默认路径我的static目录结构是这样的index.html中这样引用c
- 1.为什么要使用synchronized在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字syn
- 前言当您使用LINQ来处理数据库时,这种体验是一种神奇的体验,对吗?你把数据库实体像一个普通的收集,使用Linq中像Where,Select
- 本文实例讲述了Java实现求解一元n次多项式的方法。分享给大家供大家参考,具体如下:项目需要做趋势预测,采用线性拟合、2阶曲线拟合和指数拟合
- 如今,企业级应用程序的常见场景是同时支持HTTP和HTTPS两种协议,这篇文章考虑如何让Spring Boot应用程序同时支持HTTP和HT
- 首先请看如下代码:public class generictype { public static void main(String str
- Java的集合类是一种特别有用的工具,它可以用于存储数量不等的多个对象,并可以实现常用的数据结构,如栈、队列等。Java集合还可以用于板寸具
- 前言各位精通CRUD的老司机,相信大家在工作中mybatis或者mybatisplus使用的肯定是比较多的,那么大家或多或少都应该对下面的行
- 题目给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:输入: "abcabcbb&qu
- 嵌入式Servlet容器在Spring Boot中,默认支持的web容器有 Tomcat, Jetty, 和 Undertow1、原理分析那
- 为什么要重复造轮子你可能会问,Spring已经自带了全局异常拦截,为什么还要重复造轮子呢?这是个好问题,我觉得有以下几个原因装逼Spring
- Cookie和Session都是为了保持用户的访问状态,一方面为了方便业务实现,另一方面为了简化服务端的程序设计,提高访问性能。Cookie
- 1. 为什么写这篇文章?事情是这样的,在 2021年6月10日早上我在CSDN上发布了文章《你真的懂Java怎么输出Hello World吗
- 对某个类型中的方法进行拦截,然后加入固定的业务逻辑,这是AOP面向切面编程可以做的事,在springboot里实现aop的方法也有很多, s
- 目录(1)class常量池(2)运行时常量池(3)基本类型包装类常量池(4)字符串常量池总结java中有几种不同的常量池,以下的内容是对ja
- 关于静态类型检查和动态类型检查的解释:静态类型检查:基于程序的源代码来验证类型安全的过程;动态类型检查:在程序运行期间验证类型安全的过程;J
- 1、购买或本地生成ssl证书要使用https,首先需要证书,获取证书的两种方式:1、自己通过keytool生成2、通过证书授权机构购买###
- 本文介绍通过Java程序批量替换PDF中的指定文本内容。程序环境准备如下:程序使用环境如图,需要注意的是,本文使用了免费版的PDF jar工
- 本文实例为大家分享了JavaMail实现带附件的邮件发送的具体代码,供大家参考,具体内容如下发送纯文本的邮件package com.haiw
- 实现的功能比较简单,就是随机产生了四个字符然后输出。效果图如下,下面我会详细说一下实现这个功能用到了那些知识点,并且会把 这些知识点详细的介