自定义MyBatis插件

news2024/12/24 9:58:03

插件原理回顾

在前面,我们通过 MyBatis插件机制介绍与原理 分析了 MyBatis 插件的基本原理,但是可能还只是理论上的分析,没有实战的锻炼可能理解的还是不够透彻。接下来,我们通过自定义插件实例来进一步深度理解 MyBatis 插件的插件机制。

插件接口

  • MyBatis 插件接口-Interceptor 有哪些方法?

    • intercept ​方法,插件的核心方法
    • plugin ​方法
    • setProperties ​方法

自定义插件

现在,我们从零开始,设计实现一个自定义插件。

  1. 新建一个 Maven 项目,然后导入 Mybatis 对应 jar 包

     <!--mybatis坐标-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.6</version>
            </dependency>
            <dependency>
                <groupId>org.jboss</groupId>
                <artifactId>jboss-vfs</artifactId>
                <version>3.2.15.Final</version>
            </dependency>
            <!--mysql驱动坐标-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.16</version>
                <scope>runtime</scope>
            </dependency>
    
  2. 接下来,完善 sqlMapConfig.xml、jdbc.properties 等

   <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 加载外部的propeties文件 -->
    <properties resource="jdbc.properties"/>

    <settings>
        <!-- 输出日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!-- 为实体的全限定类名取别名 -->
    <typeAliases>
        <!-- 给单独的实体起别名 -->
        <!-- <typeAlias type="space.terwer.pojo.User" alias="user"/> -->

        <!-- 批量起别名:改包下所有类本身的类名,不区分大小写 -->
        <package name="space.terwe.pojo"/>
    </typeAliases>

    <!-- environments:运行环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 当前事务交给JDBC管理 -->
            <transactionManager type="JDBC"/>
            <!-- 当前使用MyBatis提供的连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <environment id="production">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 引入映射配置文件 -->
    <mappers>
       <!--
       <mapper class="space.terwer.mapper.IUserMapperr"/>
       -->
       <package name="space.terwer.mapper"/>
    </mappers>
</configuration>
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false
jdbc.username=terwer
jdbc.password=123456

pojo 和 mapper

package space.terwer.pojo;

import java.io.Serializable;

/**
 * @author terwer on 2024/6/13
 */
public class User implements Serializable {
    private Integer id;
    private String username;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}
package space.terwer.mapper;

import space.terwer.pojo.User;

import java.util.List;

/**
 * @author terwer on 2024/6/13
 */
public interface IUserMapper {
    /**
     * 查询用户
     */
    List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="space.terwer.mapper.IUserMapper">
    <resultMap id="userMap" type="space.terwer.pojo.User">
        <result property="id" column="id"></result>
        <result property="username" column="username"></result>
    </resultMap>

    <!-- resultMap:手动配置实体属性与表字段的映射关系 -->
    <select id="findAll" resultMap="userMap">
        select id, username from user
    </select>
</mapper>
  1. 编写测试用例,让 mybatis 先跑起来

    package space.terwer;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Before;
    import org.junit.Test;
    import space.terwer.mapper.IUserMapper;
    import space.terwer.pojo.User;
    
    import java.io.InputStream;
    import java.util.List;
    
    import static org.junit.Assert.assertTrue;
    
    /**
     * @author terwer on 2024/6/13
     */
    public class MainTest {
        private IUserMapper userMapper;
        private SqlSession sqlSession;
    
        @Before
        public void before() throws Exception {
            System.out.println("before...");
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            sqlSession = sqlSessionFactory.openSession();
            // 这样也是可以的,这样的话后面就不用每次都设置了
            // sqlSession = sqlSessionFactory.openSession(true);
            userMapper = sqlSession.getMapper(IUserMapper.class);
        }
    
        @Test
        public void testFindAll() {
            List<User> all = userMapper.findAll();
            for (User user : all) {
                System.out.println(user);
            }
        }
    }
    
    

    效果如下:

    Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1ed1993a]
    ==>  Preparing: select id, username from user
    ==> Parameters: 
    <==    Columns: id, username
    <==        Row: 1, lisi
    <==        Row: 2, tom
    <==        Row: 8, 测试2
    <==        Row: 9, 测试3
    <==      Total: 4
    User{id=1, username='lisi'}
    User{id=2, username='tom'}
    User{id=8, username='测试2'}
    User{id=9, username='测试3'}
    

    此时,整个项目结构如下:

    image

  2. 编写插件 MyPlugin

    package space.terwer.plugin;
    
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.Interceptor;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.plugin.Signature;
    
    import java.sql.Connection;
    import java.util.Properties;
    
    /**
     * @author terwer on 2024/6/13
     */
    @Intercepts({
            @Signature(
                    type = StatementHandler.class,
                    method = "prepare",
                    args = {Connection.class, Integer.class}
            )
    })
    public class MyPlugin implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 增强逻辑
            System.out.println("这里是插件的增强方法....");
            // 执行原方法
            return invocation.proceed();
        }
    
        /**
         * 主要是为了把这个拦截器生成一个代理放到拦截器链中 * ^Description包装目标对象 为目标对象创建代理对象 * @Param target为要拦截的对象
         */
        @Override
        public Object plugin(Object target) {
            System.out.println("将要包装的目标对象:" + target);
            return Interceptor.super.plugin(target);
        }
    
        /**
         * 获取配置文件的属性,插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
         **/
        @Override
        public void setProperties(Properties properties) {
            System.out.println("插件配置的初始化参数:" + properties);
            Interceptor.super.setProperties(properties);
        }
    }
    
    

    将插件配置到 sqlMapConfig.xm l 中。

    <plugins>
        <plugin interceptor="space.terwer.plugin.MyPlugin">
            <property name="param1" value="value1"/>
        </plugin>
    </plugins>
    

    查看效果

    Using VFS adapter org.apache.ibatis.io.JBoss6VFS
    插件配置的初始化参数:{param1=value1}
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    Checking to see if class space.terwer.mapper.IUserMapper matches criteria [is assignable to Object]
    将要包装的目标对象:org.apache.ibatis.executor.CachingExecutor@262b2c86
    将要包装的目标对象:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@c81cdd1
    将要包装的目标对象:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@289d1c02
    将要包装的目标对象:org.apache.ibatis.executor.statement.RoutingStatementHandler@17d0685f
    Opening JDBC Connection
    Created connection 1183888521.
    Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4690b489]
    这里是插件的增强方法....
    ==>  Preparing: select id, username from user
    ==> Parameters: 
    <==    Columns: id, username
    <==        Row: 1, lisi
    <==        Row: 2, tom
    <==        Row: 8, 测试2
    <==        Row: 9, 测试3
    <==      Total: 4
    User{id=1, username='lisi'}
    User{id=2, username='tom'}
    User{id=8, username='测试2'}
    User{id=9, username='测试3'}
    

    可以看到,插件确实生效了。

总结

通过上面的自动插件实例,我再来进一步分析一下:

在四大对象创建的时候

1、每个创建出来的对象不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler)​;

2、获取到所有的 Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target)​,返回 target 包装后的对象;

3、插件机制:我们可以使用插件为目标对象创建一个代理对象 AOP (面向切面);我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行;

那么,插件具体是如何拦截并附加额外的功能的呢?以 ParameterHandler 来说:

// org.apache.ibatis.session.Configuration
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

interceptorChain​ 保存了所有的拦截器(interceptors),是 mybatis 初始化的时候创建的。调用拦截器链 中的拦截器依次的对目标进行拦截或增强。interceptor.plugin(target) ​中的 target 就可以理解为 mybatis 中的四大对象。返回 的 target 是被重重代理后的对象。

// org.apache.ibatis.plugin.InterceptorChain

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

例如:如果我们想要拦截 Executor 的 query 方法,那么可以稍微修改一下,这样定义插件:

@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
public class ExeunplePlugin implements Interceptor {
	// TODO
}

这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。待准备工作做完后,MyBatis 处于就绪状态。我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor 实例创建完毕后,MyBatis 会通过 JDK 动态代理为 实例生成代理类。这样,插件逻辑即可在 Executor 相关方法被调用前执行。

数据库脚本

-- show databases;
-- select version();
-- drop user 'terwer'@'%';
-- CREATE USER 'terwer'@'%' IDENTIFIED BY '123456';
-- GRANT ALL PRIVILEGES ON *.* TO 'terwer'@'%' WITH GRANT OPTION;
-- flush privileges;
-- create database test default character set utf8 collate utf8_general_ci;

-- user
create table if not exists user
(
    id       int auto_increment
        primary key,
    username varchar(50) null,
    password varchar(50) null,
    birthday varchar(50) null
)
    charset = utf8;

-- user data
INSERT INTO test.user (id, username, password, birthday) VALUES (1, 'lisi', '123', '2019-12-12');
INSERT INTO test.user (id, username, password, birthday) VALUES (2, 'tom', '123', '2019-12-12');
INSERT INTO test.user (id, username, password, birthday) VALUES (8, '测试2', null, null);
INSERT INTO test.user (id, username, password, birthday) VALUES (9, '测试3', null, null);

本文源码

mybatis-plugin

文章更新历史

2024/06/13 初稿

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

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

相关文章

【gitcode】idea 在本地拉取和push本地代码到gitcode仓库

【首次使用】 1、idea 拉取代码&#xff0c;很容易这里就不记录了。 2、push代码时&#xff0c;总是弹窗登录输入在gitcode.com登录能成功。但是在idea 怎么都不成功。控制台提示 remote: HTTP Basic: Access denied fatal: Authentication failed for ******* 认证失败 3…

如何解决javadoc一直找不到路径的问题?

目录 一、什么是javadoc二、javadoc为什么会找不到路径三、如何解决javadoc一直找不到路径的问题 一、什么是javadoc Javadoc是一种用于生成Java源代码文档的工具&#xff0c;它可以帮助开发者生成易于阅读和理解的文档。Javadoc通过解析Java源代码中的注释&#xff0c;提取其…

Docker:在DockerHub上创建私有仓库

文章目录 Busybox创建仓库推送镜像到仓库 本篇开始要学习在DockerHub上搭建一个自己的私有仓库 Busybox Busybox是一个集成了三百多个最常用Linux命令和工具的软件&#xff0c;BusyBox包含了很多工具&#xff0c;这里拉取该镜像推送到仓库中&#xff1a; 安装 apt install …

Javaweb06-Jsp技术

Jsp技术 一.Jsp的运行原理 **概述&#xff1a;**JSP是Java服务器页面&#xff0c;既可以写静态页面代码&#xff0c;也可以写动态页面代码 **特点&#xff1a;**跨平台性&#xff0c;业务代码相分离&#xff0c;组件重用&#xff0c;预编译 运行原理&#xff1a; 客户端发生…

页面置换算法的模拟实现

一. 实验内容 1. 假设某一个进程&#xff0c;在运行过程中需要访问的内容依次在320个地址中。为了模拟产生320个地址的值。首先实现在main函数中调用下面的函数随机产生320个地址的地址序列。 #include<unistd.h> void randarray(int a[],int k) { int i; float s;…

使用自定义注解进行权限校验

一&#xff0c;前言 对于一些重复性的操作我们可以用提取为util的方式进行处理&#xff0c;但也可以更简便一些&#xff0c;比如自定义个注解进行。选择看这篇文章的小伙伴想必都对注解不陌生&#xff0c;但是可能对它的工作原理不太清楚。这里我们用注解实现对接口的权限校验…

长亭网络通信基础2

DNAT 默认路由配置方式 VRRP 买两个路由器 拥有两个虚拟网关地址 当一个路由器坏了还能进行正常上网 相当于三层闭合 提供功能 长亭笔试考题解析 三层交换机结合了路由器和交换机的特点 交换机没有路由表 不在同一网段 不能直接通信 可以给交换机VLANif 给接口配置ip地址 先…

【云岚到家】-day03-1-门户等缓存方案选择

【云岚到家】-day03-1-门户-缓存方案选择 1 门户1.1 门户简介1.2 常见的技术方案1.2.1 需求1.2.2 常见门户1.2.2.1 Web门户1.2.2.2 移动应用门户1.2.2.3 总结 2 缓存技术方案2.1 需求分析2.1.1 界面原型2.2.2 缓存需求 3 SpringCache入门3.1 基础概念3.1.1 Redis客户端3.1.2 Sp…

循环队列

循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;First In First Out&#xff0c;先进先出&#xff09;原则并且队尾被连接在队首以形成一个循环。 这种结构克服了普通队列在元素入队和出队时需要移动大量元素的缺点。 在循环队列中&#xff0c;当元素…

轻松上手MYSQL:探索MySQL索引数据结构的奥秘-让数据库飞起来

​&#x1f308; 个人主页&#xff1a;danci_&#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL》&#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索MYSQL索引数据结构之旅✨ &#x1f44b; 大家好&#xff01;文本学习研…

Hadoop的读写流程

Hadoop分布式文件系统(HDFS)是Apache Hadoop项目的核心组件,它为大数据存储提供了一个可靠、可扩展的存储解决方案。本文将详细介绍HDFS的读写数据流程,包括数据的存储原理、读写过程以及优化策略。 一、HDFS简介 HDFS是一个高度容错的分布式文件系统,它设计用于运行在通…

python包管理器--- pip、conda、mamba的比较

1 pip 1.1 简介 pip是一个 Python 的包&#xff08;Package&#xff09;管理工具&#xff0c;用于从 PyPI 安装和管理 Python 标准库之外的其他包&#xff08;第三方包&#xff09;。从 Python 3.4 起&#xff0c;pip 已经成为 Python 安装程序的一部分&#xff0c;也是官方标准…

【chatbot-api开源项目】开发文档

chatbot-api 1. 需求分析1-1. 需求分析1-2. 系统流程图 2. 技术选型3. 项目开发3-1. 项目初始化3-2. 爬取接口获取问题接口回答问题接口创建对应对象 3-3. 调用AI3-4. 定时自动化回答 4. Docker部署5. 扩展5-1. 如果cookie失效了怎么处理5-2. 如何更好的对接多个回答系统 Gitee…

各种机器学习算法的应用场景分别是什么(比如朴素贝叶斯、决策树、K 近邻、SVM、逻辑回归最大熵模型)?

2023简直被人工智能相关话题席卷的一年。关于机器学习算法的热度&#xff0c;也再次飙升&#xff0c;网络上一些分享已经比较老了。那么今天借着查询和学习的机会&#xff0c;我也来浅浅分享下目前各种机器学习算法及其应用场景。 为了方便非专业的朋友阅读&#xff0c;我会从算…

揭秘神秘的种子:Adobe联合宾夕法尼亚大学发布文本到图像扩散模型大规模种子分析

文章链接&#xff1a;https://arxiv.org/pdf/2405.14828 最近对文本到图像&#xff08;T2I&#xff09;扩散模型的进展促进了创造性和逼真的图像合成。通过变化随机种子&#xff0c;可以为固定的文本提示生成各种图像。在技术上&#xff0c;种子控制着初始噪声&#xff0c;并…

FineReport简单介绍(2)

一、报表类型 模板设计是 FineReport 学习过程中的主要难题所在&#xff0c;FineReport 模板设计主要包括普通报表、聚合报表、决策报表三种设计类型。 报表类型简介- FineReport帮助文档 - 全面的报表使用教程和学习资料 二、聚合报表 2-1 介绍 聚合报表指一个报表中包含多个…

电商秒杀系统

一&#xff0c;细节 二&#xff0c;需要注意的细节 1.库存超卖问题 使用mysql数据库的 悲观锁 机制。在事务中使用 for update 语句&#xff0c;此时数据库会加锁&#xff0c;其他想要当前读的线程都会被阻塞&#xff0c;在事务处理完成之后释放这一条数据。该方法的缺点在于…

排序——希尔排序

希尔排序实际上是插入排序的优化&#xff0c;所以要先介绍插入排序。 目录 插入排序 思想 演示 代码实现 总结 希尔排序 思想 演示 代码 总结 插入排序 思想 又称直接插入排序。它的基本思想是将一个值插入到一个有序序列中。直至将所有的值都插入完。 演示 假设数…

工程设计问题---行星轮系设计问题

该问题的主要目标是使汽车传动比的最大误差最小。为了使最大误差最小&#xff0c;对自动行星传动系统的齿轮齿数进行了计算。该问题包含6个整数变量和11个不同几何约束和装配约束的约束。 参考文献&#xff1a; Abhishek Kumar, Guohua Wu, Mostafa Z. Ali, Rammohan Malliped…

国际统计年鉴(1995-2023年)

数据年份&#xff1a;1995-2023 数据格式&#xff1a;pdf、excel 数据内容&#xff1a;《国际统计年鉴》是一部综合性的国际经济社会统计资料年刊&#xff0c;收录了世界200多个国家和地区的统计数据&#xff0c;并对其中40多个主要国家和地区的经济社会发展指标及国际组织发布…