干翻Mybatis源码系列之第十篇:Mybatis拦截器基本开发、基本使用和基本细节分析

news2024/10/7 2:24:27

给自己的每日一句

不从恶人的计谋,不站罪人的道路,不坐亵慢人的座位,惟喜爱耶和华的律法,昼夜思想,这人便为有福!他要像一棵树栽在溪水旁,按时候结果子,叶子也不枯干。凡他所做的尽都顺利。

本文内容整理自《孙哥说Mybatis系列视频课程》,老师实力十分雄厚,B站搜孙帅可以找到本人

前言

Mybatis拦截器的开发基本上包含两个步骤:编码和配置。
拦截器编码当中需要实现拦截器的接口,在这个类上边基于注解标注我们需要拦截的目标。这就是自定义拦截器了。

一:拦截器接口说明

public interface Interceptor {
  //拦截前需要实现的功能+放行执行具体的Dao中的方法。
  Object intercept(Invocation invocation) throws Throwable;

  //这个方法的作用就是把这个拦截器的目标,传递给下一个拦截器。这种情况下适用于多个拦截器的存在
  //当第一个拦截器处理完毕之后,把处理完毕的目标,传递给下一个拦截器。
  //这个方法涉及的是目标传递的过程。
  Object plugin(Object target);
 
  //获取拦截器相关参数的
  void setProperties(Properties properties);

}

这里边真正起拦截作用的是intercept方法。

二:拦截器实现示意

1:拦截器编码

@Intercepts({
        @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 MyMybatisInterceptor implements Interceptor {
    private String test;
    private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor.class);

    @Override
    /**
     *  作用:执行的拦截功能 书写在这个方法中.
     *       放行
     */
    public Object intercept(Invocation invocation) throws Throwable {
        if (log.isDebugEnabled())
            log.debug("----拦截器中的 intercept 方法执行------  "+test);
        return invocation.proceed();
    }

    /*
     *  把这个拦截器目标 传递给 下一个拦截器
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    /*
     *  获取拦截器相关参数的
     */
    @Override
    public void setProperties(Properties properties) {
       this.test = properties.getProperty("test");
    }
}
@Intercepts({
        @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,args是方法中的参数,这个方法要严格和这个参数对应上。这样Mybatis就能唯一的确认要拦截哪个方法了

2:拦截器配置

后续我们要在Mybatis当中配置添加拦截器配置,通知Mybatis启动的时候加载当前开发的拦截器。

    <plugins>
        <plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor"/>
    </plugins>

这里边老铁们可能会有一个问题,咱们这个不是拦截的是Executor的query方法么,然后这里边真正走数据库查询的时候不是走的StatementHandler当中的方法么?这种理解是没有问题的,咱们拦截的是Executor当中的方法,但是Executor当中执行query的时候,底层走的也是StatementHandler当中的方法。拦住了Executor就相当于拦截住了StatementHandler当中的query。那同样拦截住了

三:拦截器作用示范

    /**
     * 用于测试:Plugins的基本使用
     */
    @Test
    public void test1() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();

        UserDAO userDAO = session.getMapper(UserDAO.class);

        User user = userDAO.queryUserById(4);
        System.out.println("user = " + user);

        User newUser = new User(4, "xiaohuahua");
        userDAO.update(newUser);

        session.commit();
    }

执行结果如下:

2023-06-15 20:13:02 DEBUG LogFactory:135 - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2023-06-15 20:13:02 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 20:13:02 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 20:13:02 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 20:13:02 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 20:13:02 DEBUG MyMybatisInterceptor:28 - ----拦截器中的 intercept 方法执行------  111111
2023-06-15 20:13:02 DEBUG UserDAO:62 - Cache Hit Ratio [com.baizhiedu.dao.UserDAO]: 0.0
2023-06-15 20:13:02 DEBUG JdbcTransaction:137 - Opening JDBC Connection
2023-06-15 20:13:02 DEBUG PooledDataSource:406 - Created connection 1561408618.
2023-06-15 20:13:02 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5d11346a]
2023-06-15 20:13:02 DEBUG queryUserById:159 - ==>  Preparing: select id,name from t_user where id = ? 
2023-06-15 20:13:02 DEBUG queryUserById:159 - ==> Parameters: 4(Integer)
2023-06-15 20:13:02 DEBUG queryUserById:159 - <==      Total: 1
user = User{id=4, name='二姐'}
2023-06-15 20:13:02 DEBUG MyMybatisInterceptor:28 - ----拦截器中的 intercept 方法执行------  111111
2023-06-15 20:13:02 DEBUG update:159 - ==>  Preparing: update t_user set name=? where id=? 
2023-06-15 20:13:02 DEBUG update:159 - ==> Parameters: xiaohuahua(String), 4(Integer)
2023-06-15 20:13:02 DEBUG update:159 - <==    Updates: 1
2023-06-15 20:13:02 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5d11346a]

Process finished with exit code 0

我们开发完毕拦截器之后,Mybatis启动的时候自动为我们进行加载,运行的时候自动走拦截器。

四:拦截器作用解析

1:如何给拦截器注入参数?

首先需要在Mybatis-config.xml当中通过property标签配置拦截器属性

    <plugins>
        <plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor">
            <property name="test" value="111111"/>
        </plugin>
    </plugins>

然后在MyMybatisInterceptor拦截器初始化的时候,基于其中setProperties方法进行拦截器属性赋值,赋值之后在跑动的时候就可以在使用这些属性了。

    @Override
    public void setProperties(Properties properties) {
       this.test = properties.getProperty("test");
    }

上边日志中的:

2023-06-15 20:13:02 DEBUG MyMybatisInterceptor:28 - ----拦截器中的 intercept 方法执行------  111111

不就是最好的说明么?

2:如何配置拦截多个方法?

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}
@Intercepts({
        @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})
})

@Intercepts({})这里边的{}就代表了数组,如果只有一个数据的话,{}是可以去掉的。

五:拦截器细节分析

1:拦截器想要拦截SQL,如何拦截最合适?

在这里插入图片描述

Executor的功能是比较繁杂的,有增删改查包括事务的一些操作,而他真正增删改查的操作是交给StatementHandler来做,StatementHandler当中的操作就比较单一了,所以我们把拦截放到StatementHandler上是比较合理的。statementHandler当中只有两个query和一个update,然后里边还有一个prepare方法(BaseStatementHandler当中写的,完成Mybatis当中所有的Statement对象的创建),这个方法的作用是准备Statement给StatementHandler中的query和update使用。

Ps:StatementHandler使用的是装饰器设计模式,然后也有适配器设计模式。

在这里插入图片描述

所以,如果拦截器的目的是获取SQL的话,最合适的方法就是拦截BaseStatementHandler当中的prepare这个唯一生产Statement对象的方法。

而且,我们观察下prepare这个方法:

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

这里边我们看到这个方法里边有Connection,有了连接对象之后,我们就可以拿到所有的JDBC中的对象。

2:拦截器想要拦截SQL,为什么这么合适?

1:BaseStatementHandler中的prepare方法生产所有的Statement对象。
2:prepare方法里边有Connection,有了连接对象之后,我们就可以拿到所有的JDBC中的对象

3:拦截器拦截prepare方法测试

public abstract class MyMybatisInterceptorAdapter implements Interceptor {
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }
}
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyMybatisInterceptor2 extends MyMybatisInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor2.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (log.isDebugEnabled())
            log.debug("----拦截器中的 MyMybatisInterceptor2   intercept方法执行------  "+test);

        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

    <plugins>
        <plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor2"/>
    </plugins>

执行结果如下:

2023-06-15 21:16:03 DEBUG LogFactory:135 - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2023-06-15 21:16:03 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:16:03 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:16:03 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:16:03 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:16:04 DEBUG UserDAO:62 - Cache Hit Ratio [com.baizhiedu.dao.UserDAO]: 0.0
2023-06-15 21:16:04 DEBUG JdbcTransaction:137 - Opening JDBC Connection
2023-06-15 21:16:04 DEBUG PooledDataSource:406 - Created connection 929776179.
2023-06-15 21:16:04 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@376b4233]
2023-06-15 21:16:04 DEBUG MyMybatisInterceptor2:25 - ----拦截器中的 MyMybatisInterceptor2   intercept方法执行------  
2023-06-15 21:16:04 DEBUG queryUserById:159 - ==>  Preparing: select id,name from t_user where id = ? 
2023-06-15 21:16:04 DEBUG queryUserById:159 - ==> Parameters: 4(Integer)
2023-06-15 21:16:04 DEBUG queryUserById:159 - <==      Total: 1

Process finished with exit code 0

4:如何拦截器中获取SQL

我们先找到boundSql对象
在这里插入图片描述

具体的代码如下,有两种实现方式:

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //RoutingStatementHandler---delegate---
        //RoutingStatementHandler satementHandler = (RoutingStatementHandler) invocation.getTarget();
        //BoundSql boundSql = satementHandler.getBoundSql();
        //String sql = boundSql.getSql();
        //log.info("sql:",sql);

		基于Mybatis提供的反射工厂来干。直接打破封装用反射即可,以下是Mybatis低等用于反射的对象。
        MetaObject metaObject = SystemMetaObject.forObject(invocation);
        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
        if (log.isDebugEnabled()) {
            log.debug("sql : " + sql);
        }
        return invocation.proceed();
    }

具体的实现结果如下:

Connected to the target VM, address: '127.0.0.1:34056', transport: 'socket'
2023-06-15 21:26:00 DEBUG LogFactory:135 - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2023-06-15 21:26:00 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:26:00 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:26:00 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:26:00 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:38:58 DEBUG UserDAO:62 - Cache Hit Ratio [com.baizhiedu.dao.UserDAO]: 0.0
2023-06-15 21:38:58 DEBUG JdbcTransaction:137 - Opening JDBC Connection
2023-06-15 21:38:58 DEBUG PooledDataSource:406 - Created connection 1906879951.
2023-06-15 21:38:58 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@71a8adcf]
2023-06-15 21:38:58 DEBUG MyMybatisInterceptor2:25 - ----拦截器中的 MyMybatisInterceptor2   intercept方法执行------  
2023-06-15 21:38:58 DEBUG queryUserById:159 - ==>  Preparing: select id,name from t_user where id = ? 
2023-06-15 21:38:58 DEBUG queryUserById:159 - ==> Parameters: 4(Integer)
2023-06-15 21:38:58 DEBUG queryUserById:159 - <==      Total: 1
Disconnected from the target VM, address: '127.0.0.1:34056', transport: 'socket'

Process finished with exit code 0

5:细节说明

        MetaObject metaObject = SystemMetaObject.forObject(invocation);
        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
        if (log.isDebugEnabled()) {
            log.debug("sql : " + sql);
        }

在这里插入图片描述

Mybatis当中很多反射操作都是这么干的,这样写更加Mybatis一点,这样操作是基于对象从属的层级一层一层点进去的,当然如果我们想要去给他这样赋值也是可以的。

        String sql = (String) metaObject.set("target.delegate.boundSql.sql","select * from user where id = ?");

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

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

相关文章

使用终极 GUI 框架 NiceGUI 提升你的 Python 应用程序

介绍 随着机器学习和深度学习网站应用程序的快速增长&#xff0c;开发人员一直在寻找新的 Web 框架&#xff0c;以便更轻松地构建这些网站应用程序。 数据科学应用程序的普及程度已经上升&#xff0c;因此新的框架也在增加。开发人员创建了许多新的框架&#xff0c;这些框架被证…

【压缩技巧】如何把文件压缩成RAR?如何解压?

在生活及工作中&#xff0c;我们经常需要压缩文件使其更方便存储或传送。 压缩文件的格式有很多种&#xff0c;RAR是其中一种常见的格式&#xff0c;也是WinRAR软件独有的压缩格式。所以想要把文件压缩成RAR&#xff0c;就可以通过WinRAR来操作。 压缩方法如下&#xff1a; 1…

数组的原型方法-es6

数组的原型方法-es6Array.form()Array.of() find() 和 findIndex()copyWithin()fill()entries(),keys()和values()includes()flat()和flatMap()扩展运算符at()reduce()和reduceRight()some()判断数组中是否存在满足条件的项 18、Array.form() Array.from方法用于将两类对象转…

linuxOPS系统服务_linux高级命令

find命令 find 路径 [选项 选项的值] … 选项作用-name根据文件的名称进行-type按文件类型进行搜索&#xff0c;f代表普通文件&#xff0c;d代表文件夹 find命令查找文件 示例1 查找一个文件 案例1 ,在linux整个系统中查找 test.txt文件 find / -name test.txt -type f案例…

《Opencv3编程入门》学习笔记—第六章

《Opencv3编程入门》学习笔记 记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。 第六章 图像处理 一、线性滤波&#xff1a;方框滤波、均值滤波、高斯滤波 &#xff08;一&#xff09;平滑处理 平滑处理也称模糊处理&#xff0c;是一种简单且使用频率…

像拿快递一样取文件的FileCodeBox

本文大约完成于 2月中旬&#xff0c;镜像中途有过更新&#xff1b; 什么是 FileCodeBox &#xff1f; FileCodeBox 中文名是 文件快递柜&#xff0c;取文件像取快递一样&#xff0c;支持通过匿名口令分享文本&#xff0c;文件。 很多时候&#xff0c;我们都想将一些文件或文本传…

流量卡和手机上显示的流量对不上,这是怎么回事呢?

很多朋友反映&#xff0c;自己购买的流量卡和手机上显示的流量对不上&#xff0c;这到底是怎么回事呢&#xff1f;小编认为&#xff0c;如果出现这种情况&#xff0c;无非有两种因素&#xff1a; ​ 一、买了不正规的流量卡&#xff0c;也就是大家所说的物联卡。 物联卡的特点…

低压电源系统中防雷浪涌保护器的布局方案

低压电源系统是工业、商业和住宅领域中广泛应用的电力系统&#xff0c;其供电电压一般在1000V以下。由于雷击和浪涌等自然灾害的存在&#xff0c;低压电源系统需要防雷浪涌保护器来保护系统不受损坏。本文将详细介绍低压电源系统中防雷浪涌保护器的布置位置与规范。 防雷浪涌保…

AUTOSAR】UDS协议的代码分析与解读(八)----UDS安全访问27h和通讯控制28h

8.9 安全访问 Security Access (27h) 由于保密、排放或安全的原因&#xff0c; 安全访问服务提供一种方法以方便访问受限制的数据或诊断服务。 支持安全访问的ECU应该实现种子和密钥的合理算法&#xff0c;该算法应在特定文档中记录&#xff0c;而只有特定的少数 人可以访问该文…

FasterTransformer 003: CMAKELIST in gemm_test

cmake -DSM60 -DCMAKE_BUILD_TYPERelease .. -DCMAKE_BUILD_TYPE cmake编译类型通常默认为debug&#xff0c;但是在编译软件时&#xff0c;一般都需要使用release版本的&#xff0c;debug太慢了。设置为release版本可以在cmake文件里进行&#xff0c;也可以在运行cmake命令时使…

深度学习实践篇[17]:模型压缩技术、模型蒸馏算法:Patient-KD、DistilBERT、DynaBERT、TinyBERT

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

观点碰撞燃爆会场,2023开放原子全球开源峰会区块链分论坛圆满落幕!

2023开放原子全球开源峰会区块链分论坛活动现场 6月13日&#xff0c;由开放原子开源基金会主办&#xff0c;XuperCore开源项目承办&#xff0c;北京百度网讯科技有限公司、招商银行、联通数字科技有限公司、杭州趣链科技有限公司等协办的2023开放原子全球开源峰会区块链分论坛在…

《低代码指南》不能“生成代码”的低代码平台,为什么推进阻力那么大?

为什么现在低代码平台推进阻力那么大? “在踏出一步之前,首先考虑能否退回去” 现在低代码平台,功能性能这些先不说,能不能提升效率,提升多少,暂不讨论。光“平台和环境锁定”这一点,就是整个行业最大的技术推广障碍。道理很简单,平台有几百个,但是如果选了一个,…

c++11 标准模板(STL)(std::basic_ios)(一)

定义于头文件 <ios>template< class CharT, class Traits std::char_traits<CharT> > class basic_ios : public std::ios_base 类 std::basic_ios 提供设施&#xff0c;以对拥有 std::basic_streambuf 接口的对象赋予接口。数个 std::basic_ios 对…

汽车IVI中控开发入门及进阶(八):视频相关的一些知识点

前言: 视频最早的渊源来源于电视。做汽车仪表/IVI中控,尤其是IVI信息娱乐部分,都要涉及到视频这个知识点,各种概念很多,首先需要明确一条主线,那就是SDTV标清电视->HDTV高清电视->UHDTV超高清电视的一个发展脉络,BT601/656是SDTV标清电视接口,BT1120则对应HDTV高…

SpringCloud:什么是SpringCloud?①

一、什么是SpringCloud 像“云朵”一样聚集起来管理服务。它的定位就是&#xff1a;服务群组间的通讯管理。 二、Spring&#xff0c;SpringBoot以及SpringCloud之间的关系。 Spring是一种引擎式的底层核心-- bean容器思想。后面均是基于它延伸的高级产品。 SpringBoot 专注单体…

为什么中国大公司不开发 Linux 桌面应用?

我们Linux平台C端的业务&#xff0c;也就是给大家提供的Linux的个人版本。目前真的是靠爱发电了&#xff0c;逃&#xff09; 更爱的是&#xff0c;我们不只是提供了X64平台&#xff0c;更是提供了Arm&#xff0c;MIPS64平台的二进制包。 估计国内在Linux平台首发新功能的桌面应…

马尔科夫模型 详解

马尔可夫性 马尔可夫性也叫做无后效性、无记忆性&#xff0c;即是过去只能影响现在&#xff0c;不能影响将来。 在数学上&#xff0c;如果为一个随机过程&#xff0c;则马科尔夫性质可以符号化成如下形式&#xff1a; 从上式可知&#xff0c;过去(s)并不影响将来(th)的状态&a…

Python数据分析讲课笔记02:Numpy基础

文章目录 零、学习目标一、NumPy概述二、多维数组对象三、创建NumPy多维数组1、利用array()函数创建NumPy数组2、创建NumPy数组的其它函数&#xff08;1&#xff09;利用zeros函数创建元素全为0的数组&#xff08;2&#xff09;利用ones函数创建元素全为1的数组&#xff08;3&a…

时间序列异常检测:统计和机器学习方法介绍

在本文中将探索各种方法来揭示时间序列数据中的异常模式和异常值。 时间序列数据是按一定时间间隔记录的一系列观测结果。它经常在金融、天气预报、股票市场分析等各个领域遇到。分析时间序列数据可以提供有价值的见解&#xff0c;并有助于做出明智的决策。 异常检测是识别数…