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

news2024/11/27 4:30:38

给自己的每日一句

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

本文内容整理自《孙哥说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/651075.html

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

相关文章

微信小程序 method传参 和 页面传参

method传参 标签&#xff1a; <image src"/img/b1.jpg" classbannerImg mode"widthFix" bindtap"gotoMessage" data-flag"msg"></image> 使用data-参数Key 指定参数值 method: gotoMessage(e){ let flagName e.targe…

9. 子查询

9.1 概述 ​ 子查询指一个查询语句嵌套在另一个查询语句内部&#xff0c;这个特性从 MySQL 4.1 开始引入。 ​ 从相对位置来说&#xff0c;子查询又被称为内查询&#xff0c;主查询又被称为外查询 9.1.1 子查询的结构 子查询的结构如下所示&#xff1a; SELECT select_lis…

Apache Zeppelin系列教程第九篇——SQL Debug In Zeppelin

SQL Debug介绍 首先介绍下什么是SQL Debug&#xff1f; 但是经常有这样一个需求&#xff0c;一大段sql 跑出来之后&#xff0c;发现不是自己想要的结果&#xff1f;比如&#xff1a; demo 1: select id,name from ( select id,name from table1 union all select id,name fr…

数据库(第五章)数据库的完整性

1.数据库的正确性和相容性 正确性&#xff1a;符合现实逻辑 相容性&#xff1a;两个表中的同一对象要完全相同 如何实现数据库的完整性&#xff1f; 1.定义完整性约束条件 2.提供完整性检查方法 3.进行违约处理 完整性我们之前学过。包括三个 1.实体完整性 2.参照完整性 3.用户…

Fiddler中 AutoResponder 使用

Fiddler的 AutoResponder &#xff0c;即URL重定向功能非常强大。不管我们做URL重定向&#xff0c;还是做mock测试等&#xff0c;都可以通过该功能进行实践。 下面&#xff0c;小酋就来具体讲下该功能的用法。 如果你想学习Fiddler抓包工具&#xff0c;我这边给你推荐一套视频…

【C++进阶】带你手撕AVL树

文章目录 一、什么是AVL树二、AVL树的定义三、AVL树的插入1.理论讲解2.代码实现 四、AVL树的旋转1.左单旋2.右单旋3.左右双旋4.右左双旋 五、 AVL树的验证六、完整源码 一、什么是AVL树 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为…

Kafka集群部署

Kafka是一个高吞吐量、基于ZooKeeper&#xff08;ZooKeeper维护Kafka的broker信息&#xff09;的分布式发布订阅信息系统&#xff0c;它可以处理消费者在网站中的所有动作&#xff08;网页浏览&#xff0c;搜索和其他用户的行动&#xff09;流数据。通常情况下&#xff0c;使用…

Linux(CentOS 7)下安装配置Maven3.9.2

Linux&#xff08;CentOS 7&#xff09;下安装配置Maven3.9.2 环境 JDK 1.8OS:Centos 7.5 tar包安装 下载 apache-maven-3.9.2-bin.tar.gz https://maven.apache.org/download.cgi 安装步骤 公共服务&#xff0c;将maven安装在root用户下。 创建maven安装地址解压安装…

UE4/5样条线学习(三):样条线与时间轴

目录 简单的小模板 物品跟随样条线移动 粒子特效类&#xff1a; 简单的小模板 通过之前的案例&#xff0c;我们可以直接创建一个actor蓝图&#xff0c;加上要用的样条组件&#xff1a; 然后我们就可以通过时间轴做出不同的一些效果 在蓝图中找到时间轴的这个节点 双击时间…

Web安全测试中常见逻辑漏洞解析(实战篇)

前言&#xff1a; 越权漏洞是比较常见的漏洞类型&#xff0c;越权漏洞可以理解为&#xff0c;一个正常的用户A通常只能够对自己的一些信息进行增删改查&#xff0c;但是由于程序员的一时疏忽&#xff0c;对信息进行增删改查的时候没有进行一个判断&#xff0c;判断所需要操作的…

网络安全学习路线+自学笔记(超详细)

01 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

2023我的前端面试准备

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 前言 回顾过往&#xff0c;至今已有三个年头了&#xff0c;《2023年我的前端面试准备》在小…

我的创作纪念日——创作历程,机缘,与成就

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 写在前面 一.机缘 二.收获 三.日常 四.成就 五.憧憬与规划 六.分享 写在前面 大…

Vivado 下 IP核之FIFO 实验

目录 Vivado 下 IP核之FIFO 实验 1、FIFO IP 核简介 2、实验任务 3、程序设计 3.1、FIFO IP 核配置 3.1.1、“Basic” 选项卡下各参数配置 3.1.2、“Native Ports”选项卡下各参数配置 3.1.3、“Status Flags” 选项卡下各参数配置 3.1.4、“Data Counts&#xff08;数…

私域开始收费后,还值得做吗?

这些年&#xff0c;私域成为不少企业挖掘增量的新阵地。 私域指品牌自有的可随时、重复、低成本触达用户的场域。《2023私域运营趋势白皮书》&#xff08;后称<白皮书>&#xff09;显示&#xff0c;接受调研的企业中有70%将私域运营阵地放在腾讯旗下的企业微信。 不过&…

美的中间件面试(部分)

用redis有遇到过大key问题吗&#xff0c;怎么解决 我介绍项目中用异步解耦的思路来从mysql同步数据到redis&#xff0c;具体就是binlogkafaka。面试官问读的主mysql的binlog,还是从的binlog。A&#xff1a;主mysql。Q&#xff1a;可以用什么中间件读binlog。A&#xff1a;可以…

高效操作!linux在终端里快速跳转到文件管理器对应的目录下

在linux下&#xff0c;可能会有一个经常的操作&#xff0c;是在终端下&#xff0c;进入某个文件夹&#xff0c;用vim修改了某些操作&#xff0c;比如修改了一个html文件&#xff1b; 然后你想用chrome打开这个html文件&#xff0c;那么你可能需要打开文件管理器&#xff0c;切…

第五章 模型篇: 模型保存与加载

参考教程&#xff1a; https://pytorch.org/tutorials/beginner/basics/saveloadrun_tutorial.html 文章目录 pytorch中的保存与加载torch.save()torch.load()代码示例 模型的保存与加载保存 state_dict()nn.Module().load_state_dict()加载模型参数保存模型本身加载模型本身 c…

C语言编程语法—排序算法

一、冒泡排序 冒泡排序&#xff08;英语&#xff1a;Bubble Sort&#xff09;是一种简单的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序&#xff08;如从大到小、首字母从A到Z&#xff09;错误就把他们交换过来。 过程演示&…

ansible playbook脚本,安装LAMP套件

yum 集中式安装lamp --- - name: LAMP installhosts: dbserverstasks:- name: disable firewalldservice:name: firewalldenabled: nostate: stopped- name: disabled selinuxshell: "sudo sed -i s/SELINUXenforcing/SELINUXdisabled/g /etc/selinux/config"shell:…