首页 > 其他分享 >SpringBoot + Mybatis系列之插件机制 Interceptor

SpringBoot + Mybatis系列之插件机制 Interceptor

时间:2022-11-02 22:01:14浏览次数:95  
标签:插件 SpringBoot DB mybatis sql Mybatis class


SpringBoot + Mybatis系列之插件机制 Interceptor_spring boot

【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 执行过程来辅助说明

SpringBoot + Mybatis系列之插件机制 Interceptor_java_02


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 文件配置

III. 不能错过的源码和相关知识点

0. 项目

mybatis 系列博文

1. 微信公众号:一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛


标签:插件,SpringBoot,DB,mybatis,sql,Mybatis,class
From: https://blog.51cto.com/u_3408236/5818394

相关文章

  • [springboot, lettuce] io.lettuce.core.RedisCommandTimeoutException: Command time
    https://blog.csdn.net/zzhongcy/article/details/118935350?spm=1001.2101.3001.6650.7&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFr......
  • 无需插件,巧用Chrome浏览器实现对整个网页的高清截屏
    文/王不留(微信公众号:王不留) 我们有时需要截取网页的全图。发现一个简单方法。记录之。 这个方法需要使用Chrome浏览器。 1、输入网址: ​​https://www.google.cn/chro......
  • SpringBoot笔记:集成MyBatis
    SpringBoot中使用MyBatis与MVC中本质是一样的,只是某些配置可以直接使用注解完成,使编码更加便捷了。1.pom依赖集成MyBatis通常需要MyBatis、Spring、数据库驱动三个依赖,......
  • 博客园自定义主题中添加迷你音乐插件
    说明:这里直接介绍最简单直接的一种设置方式,想深入了解,自己DIY的,可滑到本文底部,附有其他大佬的方案。首先,进入你的博客园后台设置,在开通了JS权限(可自定义博客园主......
  • 011.Mybatis中SQL传参
    1.在good.xml中编辑sql语句(单参数)<!--单参数传递,使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数--><selectid="selectById"parameterType="Integer......
  • SpringBoot笔记:拦截器Interceptor和过滤器Filter
    一、拦截器InterceptorSpringBoot中定义拦截器与MVC中是一样的,区别在于拦截器的配置,MVC是配置在配置文件中的,SpringBoot中则是配置在配置类中的。(SpringBoot中的配置类需......
  • Notepad++ 下载及NppFtp插件配置
    Notepad++下载及NppFtp插件配置目录Notepad++下载及NppFtp插件配置前言下载修改菜单中文NppFtp插件配置前言notepad++是Windows操作系统下的一套文本编辑器软件,有完......
  • springboot使用minio分布式文件上传图片或视频
    Minio搭建先看下前端上传效果日期工具包使用的是hutool的importcn.hutool.core.date.DateUtil;接口可以上传视频和图片暂无做限制pom.xml<!--分布式存储-->......
  • SpringBoot自定义注解+异步+观察者模式实现业务日志保存
    一、前言我们在企业级的开发中,必不可少的是对日志的记录,实现有很多种方式,常见的就是基于AOP+注解进行保存,但是考虑到程序的流畅和效率,我们可以使用异步进行保存,在高并发情......
  • docker rabbitmq安装延迟插件
    首先,已经安装有rabbitmq3.8.17    1、下载插件地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/tags  2、将下载的插件上传到容器中:/p......