【SpringBoot + Mybatis系列】插件机制 Interceptor

news2024/10/6 18:23:39

【SpringBoot + Mybatis系列】插件机制 Interceptor

在 Mybatis 中,插件机制提供了非常强大的扩展能力,在 sql 最终执行之前,提供了四个拦截点,支持不同场景的功能扩展

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

本文将主要介绍一下自定义 Interceptor 的使用姿势,并给出一个通过自定义插件来输出执行 sql,与耗时的 case

I. 环境准备


1. 数据库准备

使用 mysql 作为本文的实例数据库,新增一张表

CREATE TABLE `money` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
  `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱',
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
复制代码

2. 项目环境

本文借助 SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

pom 依赖如下

<dependencies>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>
复制代码

db 配置信息 application.yml

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password:
复制代码

II. 实例演示


关于 myabtis 的配套 Entity/Mapper 相关内容,推荐查看之前的系列博文,这里就不贴出来了,将主要集中在 Interceptor 的实现上

1. 自定义 interceptor

实现一个自定义的插件还是比较简单的,试下org.apache.ibatis.plugin.Interceptor接口即可

比如定义一个拦截器,实现 sql 输出,执行耗时输出

@Slf4j
@Component
@Intercepts(value = {@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
public class ExecuteStatInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // MetaObject 是 Mybatis 提供的一个用于访问对象属性的对象
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
        BoundSql sql = statement.getBoundSql(invocation.getArgs()[1]);
        long start = System.currentTimeMillis();
        List<ParameterMapping> list = sql.getParameterMappings();
        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(sql.getParameterObject());
        List<Object> params = new ArrayList<>(list.size());
        for (ParameterMapping mapping : list) {
            params.add(Ognl.getValue(Ognl.parseExpression(mapping.getProperty()), context, context.getRoot()));
        }
        try {
            return invocation.proceed();
        } finally {
            System.out.println("------------> sql: " + sql.getSql() + "\n------------> args: " + params + "------------> cost: " + (System.currentTimeMillis() - start));
        }
    }
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}
复制代码

注意上面的实现,核心逻辑在intercept方法,内部实现 sql 获取,参数解析,耗时统计

1.1 sql 参数解析说明

上面 case 中,对于参数解析,mybatis 是借助 Ognl 来实现参数替换的,因此上面直接使用 ognl 表达式来获取 sql 参数,当然这种实现方式比较粗暴

// 下面这一段逻辑,主要是OGNL的使用姿势
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(sql.getParameterObject());
List<Object> params = new ArrayList<>(list.size());
for (ParameterMapping mapping : list) {
    params.add(Ognl.getValue(Ognl.parseExpression(mapping.getProperty()), context, context.getRoot()));
}
复制代码

除了上面这种姿势之外,我们知道最终 mybatis 也是会实现 sql 参数解析的,如果有分析过源码的小伙伴,对下面这种姿势应该比较熟悉了

源码参考自: org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters

BoundSql sql = statementHandler.getBoundSql();
DefaultParameterHandler handler = (DefaultParameterHandler) statementHandler.getParameterHandler();
Field field = handler.getClass().getDeclaredField("configuration");
field.setAccessible(true);
Configuration configuration = (Configuration) ReflectionUtils.getField(field, handler);
// 这种姿势,与mybatis源码中参数解析姿势一直
//
MetaObject mo = configuration.newMetaObject(sql.getParameterObject());
List<Object> args = new ArrayList<>();
for (ParameterMapping key : sql.getParameterMappings()) {
    args.add(mo.getValue(key.getProperty()));
}
复制代码

但是使用上面这种姿势,需要注意并不是所有的切点都可以生效;这个涉及到 mybatis 提供的四个切点的特性,这里也就不详细进行展开,在后面的源码篇,这些都是绕不过去的点

1.2 Intercepts 注解

接下来重点关注一下类上的@Intercepts注解,它表明这个类是一个 mybatis 的插件类,通过@Signature来指定切点

其中的 type, method, args 用来精确命中切点的具体方法

如根据上面的实例 case 进行说明

@Intercepts(value = {@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
复制代码

首先从切点为Executor,然后两个方法的执行会被拦截;这两个方法的方法名分别是query, update,参数类型也一并定义了,通过这些信息,可以精确匹配Executor接口上定义的类,如下

// org.apache.ibatis.executor.Executor
// 对应第一个@Signature
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
// 对应第二个@Signature
int update(MappedStatement var1, Object var2) throws SQLException;
复制代码

1.3 切点说明

mybatis 提供了四个切点,那么他们之间有什么区别,什么样的场景选择什么样的切点呢?

一般来讲,拦截ParameterHandler是最常见的,虽然上面的实例是拦截Executor,切点的选择,主要与它的功能强相关,想要更好的理解它,需要从 mybatis 的工作原理出发,这里将只做最基本的介绍,待后续源码进行详细分析

  • Executor:代表执行器,由它调度 StatementHandler、ParameterHandler、ResultSetHandler 等来执行对应的 SQL,其中 StatementHandler 是最重要的。
  • StatementHandler:作用是使用数据库的 Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的。
  • ParameterHandler:是用来处理 SQL 参数的。
  • ResultSetHandler:是进行数据集(ResultSet)的封装返回处理的,它非常的复杂,好在不常用。

借用网上的一张 mybatis 执行过程来辅助说明

原文 blog.csdn.net/weixin_3949…

2. 插件注册

上面只是自定义插件,接下来就是需要让这个插件生效,也有下面几种不同的姿势

2.1 Spring Bean

将插件定义为一个普通的 Spring Bean 对象,则可以生效

2.2 SqlSessionFactory

直接通过SqlSessionFactory来注册插件也是一个非常通用的做法,正如之前注册 TypeHandler 一样,如下

@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(
            // 设置mybatis的xml所在位置,这里使用mybatis注解方式,没有配置xml文件
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
    // 注册typehandler,供全局使用
    bean.setTypeHandlers(new Timestamp2LongHandler());
    bean.setPlugins(new SqlStatInterceptor());
    return bean.getObject();
}
复制代码

2.3 xml 配置

习惯用 mybatis 的 xml 配置的小伙伴,可能更喜欢使用下面这种方式,在mybatis-config.xml全局 xml 配置文件中进行定义

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//ibatis.apache.org//DTD Config 3.1//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 驼峰下划线格式支持 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.git.hui.boot.mybatis.entity"/>
    </typeAliases>
    <!-- type handler 定义 -->
    <typeHandlers>
        <typeHandler handler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>
    </typeHandlers>
    <!-- 插件定义 -->
    <plugins>
        <plugin interceptor="com.git.hui.boot.mybatis.interceptor.SqlStatInterceptor"/>
        <plugin interceptor="com.git.hui.boot.mybatis.interceptor.ExecuteStatInterceptor"/>
    </plugins>
</configuration>
复制代码

3. 小结

本文主要介绍 mybatis 的插件使用姿势,一个简单的实例演示了如果通过插件,来输出执行 sql,以及耗时

自定义插件实现,重点两步

  • 实现接口org.apache.ibatis.plugin.Interceptor
  • @Intercepts 注解修饰插件类,@Signature定义切点

插件注册三种姿势:

  • 注册为 Spring Bean
  • SqlSessionFactory 设置插件
  • myabtis.xml 文件配置

 

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

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

相关文章

mysql check slow_log造成锁-waiting for table level lock

背景&#xff1a; 我们在生产环境使用mysql的时候开启了slow_log 记录表&#xff0c;但有的时候由于记录数据过大&#xff0c;造成该表损坏 ERROR 1194 (HY000): Table slow_log is marked as crashed and should be repaired 这时候就想着用check table 命令来看看表是否正…

云服务器下WordPress发送邮件的设置

WordPress的邮件功能很强大&#xff0c;可以实现用户密码以往后自助恢复等问题。 WordPress默认是使用php发邮件的&#xff0c;php需要配置好smtp&#xff08;端口25&#xff09;服务器及密码。这种方式不直观&#xff0c;因此一般都用smtp插件&#xff0c;常用的插件是WP Mai…

FastDDS 源码剖析:src/cpp/fastdds 源码结构与Publisher源码分析

目录 源码结构 Publisher分析 Publisher 类分析 PublisherIImpl 类分析 源码结构 —builtin:该目录包含FastDDS使用的内置类型和协议的实现。 —core:该目录包含FastDDS库中使用的核心类和函数。这包括处理错误、管理内存和处理线程的类。 --domain:此目录包含DomainPart…

本地资源检测 自定义规则 零基础上手指南

本地资源检测是UWA推出的、面向于静态资源的全量分析。可以全面自动检测项目静态工程内各项资源、代码和设置&#xff0c;能够帮助项目组制定合理的资源与代码标准&#xff0c;及时发现潜在的性能问题和异常错误&#xff0c;建立有效的开发规范。其中“自定义规则”功能特别获得…

docker 安装zookeeper单机版

1. 安装版本3.5.7, 也可以自己去官网找到自己需要的版本复制命令即可 https://hub.docker.com/_/zookeeper/tags docker pull zookeeper:3.5.7 2. 创建映射文件夹&#xff1a; #1. 在centos中创建三个文件夹 mkdir -p /home/zookeeper/conf mkdir -p /home/zookeeper/data mkd…

图片视频抹除算法总结Inpaint

基本是从图片抹水印和视频抹水印两个方向 Video Inpainting&#xff1a;https://paperswithcode.com/task/video-inpaintingImage Inpainting&#xff1a;https://paperswithcode.com/task/image-inpainting 请根据目录查看 图片 Partial Conv 部分卷积层 源自于Image In…

游戏服务器搭建过程中Maven多模块编译遇到的一些问题

目录 1、多模块的创建 1.1 父模块的创建 1.2 删除垃圾文件 1.3 修改pom.xml 1.4 创建子模块继承 2、子模块之间的互相引用 3、多个模块间版本的管理 3.1 dependencis 3.2 dependencyManagement 4、依赖执行 5、在Spring Boot项目中加载依赖项目的组件有几种常用的方法…

十一、PBR材质金属度、粗糙度以及环境贴图的使用

Three.js——十一、PBR材质金属度、粗糙度以及环境贴图的使用 metalness金属度 金属度属性.metalness表示材质像金属的程度, 非金属材料,如木材或石材,使用0.0,金属使用1.0。 new THREE.MeshStandardMaterial({metalness: 1.0,//金属度属性 }) // 或者 // mesh.material.met…

Java面试题-并发篇(2万字带你搞定并发问题)

Java面试题-并发篇 一、线程 1. 线程和进程有什么区别&#xff1f; 线程具有许多传统进程所具有的特征&#xff0c;故又称为轻型进程(Light—Weight Process)或进程元&#xff1b;而把传统的进程称为重型进程(Heavy—Weight Process)&#xff0c;它相当于只有一个线程的任务…

山西电力市场日前价格预测【2023-07-12】

日前价格预测 预测明日&#xff08;2023-07-12&#xff09;山西电力市场全天平均日前电价为446.44元/MWh。其中&#xff0c;最高日前价格为584.92元/MWh&#xff0c;预计出现在12: 00。最低日前电价为325.62元/MWh&#xff0c;预计出现在00: 30。 价差方向预测 1&#xff1a;实…

【操作系统】磁盘调度算法 先来先服务、最短寻道时间优先、扫描算法、循环扫描算法

目录 一、实验要求二、实验原理三、实验内容四、实验结果五、总结 一、实验要求 设计程序模拟先来先服务FCFS、最短寻道时间优先SSTF、扫描算法SCAN和循环扫描算法CSCAN的工作过程。假设有n个磁道号所组成的磁道访问序列&#xff0c;给定开始磁道号m和磁头移动的方向(正向或者反…

【sgRectSelect】Vue实现拖拽鼠标圈选、划区域、框选组件:矩形区域选中checkbox,并回调相关选中、取消选中的操作

边框线虚线动画效果请参阅边框虚线滚动动画特效_虚线滚动效果_你挚爱的强哥的博客-CSDN博客【代码】边框虚线滚动动画特效。_虚线滚动效果https://blog.csdn.net/qq_37860634/article/details/130507289 碰撞检测原理请前往 原生JS完成“一对一、一对多”矩形DIV碰撞检测、碰撞…

嵌入式c语言编码规范

学习嵌入式的同学应该首先掌握嵌入式编码规范&#xff0c;这样才能更好的嵌入式系统。 下面就从这几个方面讲解一下嵌入式c编码规范。 注释风格、排版风格、头文件风格、变量定义、宏定义、函数 1 注释风格 1.1 注释的原则是有助于对程序的阅读和理解&#xff0c;注释不宜太多…

通过(.zip 压缩文件)安装及卸载MySQL

文章目录 一、MySQL安装1.下载MySQL压缩包2.将下载好的压缩包解压到一个没有中文路径的目录下3.配置MySQL环境变量4.验证是否配置成功5.初始化MySQL数据库6.注册MySQL服务7.启动MySQL服务8.修改MySQL数据库默认账户密码9.登录MySQL数据库10.退出MySQL 二、MySQL卸载1.以管理员身…

什么是EDI 180 退货授权和通知?

EDI 180 退货授权和通知是零售商和供应商在退货过程中使用的电子数据交换&#xff08;EDI&#xff09;文件。它既可以作为请求和授权&#xff0c;也可以作为通知文件。 EDI 180 的基本组成部分是什么&#xff1f; EDI 180 交易需要包括有关退货的关键信息。由于EDI 180可以双…

Linux 批量杀掉进程(包含某个关键字)

一、场景说明 现场环境有十多个包含 ”celery” 关键字的进程在运行&#xff0c;每次重启服务&#xff0c;需要将这些进行kill掉&#xff0c;然后重新启动。 可以用如下命令批量kill掉这些进程&#xff1a; kill -9 PID1 PID2 PID3 PID4.....其中&#xff0c;PID是查询到的进…

第九章——内存模型和名称空间

单独编译 C允许程序员将组件函数放在独立的文件中。下面列出了头文件中常包含的内容&#xff1a; 函数原型使用#define或const定义的符号常量结构声明类声明模板声明内联函数 将结构声明放在头文件中是可以的&#xff0c;因为它们不创建变量&#xff0c;而只是在源代码文件…

数据结构--树的存储结构

数据结构–树的存储结构 树的逻辑结构 树是 n ( n ≥ 0 &#xff09; n (n\ge0&#xff09; n(n≥0&#xff09;个结点的有限集合&#xff0c;n 0 时&#xff0c;称为空树&#xff0c;这是一种特殊情况。 在任意一棵非空树中应满足: 1&#xff09;有且仅有一个特定的称为 根 …

如何执行Photoshop脚本

环境 Photoshop: CC2017 OS: Windows 10 脚本放置位置 C:\Program Files\Adobe\Adobe Photoshop CC 2015\Presets\Scripts #也就是 PS的安装目录\Presets\Scripts

操作系统接口 MIT 6.828 - 1. Lab 01: Xv6 and Unix utilities

本文会将lab1中的思路以及知识点进行分析&#xff0c;并作为作者学习MIT 6.828的一个学习总结&#xff0c;希望能够帮助到学习该lab的同学们 中文版书籍&#xff1a;中文版书籍 实验教案地址&#xff1a;教案地址 操作系统接口 在操作系统中&#xff0c;为了能够有效地与操作系…