首页 > 其他分享 >自定义MyBatis插件

自定义MyBatis插件

时间:2024-06-13 13:45:31浏览次数:22  
标签:插件 自定义 terwer apache import MyBatis org public

插件原理回顾

在前面,我们通过 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>
    
  3. 编写测试用例,让 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

  4. 编写插件 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 初稿

标签:插件,自定义,terwer,apache,import,MyBatis,org,public
From: https://www.cnblogs.com/tangyouwei/p/18245705/custom-mybatis-plug-in-z1o92wh

相关文章

  • pdf增强插件:Enfocus PitStop Pro 2022 for Mac 激活版
    EnfocusPitStopPro2022是一款功能强大的PDF校对和编辑软件,旨在帮助专业用户对PDF文件进行精确的预检和校对。该软件可以无缝集成到AdobeAcrobat等常用的PDF编辑工具中,提供了一系列全面的预检和编辑功能,以确保PDF文件符合印刷和出版行业的标准和规范。下载......
  • 在线CAD块表的二次开发(react浏览编辑CAD插件)
    前言在DWG数据库中,所有图块都存放在块表McDbBlockTable()中,块表中每一条记录称为图块记录对象McDbBlockTableRecord(),图块记录中存放着所有实体数据,用户可以通过改变图块的属性设置来修改其对应着的实体数据。块表操作1.获取当前控件的数据库块表我们可以通过调用mxcad中的......
  • Vue2入门之超详细教程十八-自定义指令
    Vue2入门之超详细教程十四-自定义指令1、简介定义语法分为局部自定义指令和全局自定义指令配置对象中常用的3个回调bind:指令与蒜素被插入成功时调用inserted:指令所在元素被插入页面时被调用update:指令所在模板结构被重新解析时调用备注:指令定义时不加v-,但使用时......
  • 初始MyBatis ,详细步骤运行第一个MyBatis程序,同时对应步骤MyBatis底层剖析
    1.初始MyBatis,详细步骤运行第一个MyBatis程序,同时对应步骤MyBatis底层剖析@目录1.初始MyBatis,详细步骤运行第一个MyBatis程序,同时对应步骤MyBatis底层剖析每博一文案2.前沿知识2.1框架(framework)2.2三层架构2.3分析JDBC的缺点3.初始了解MyBatis4.MyBatis第一个入门程......
  • 如何在Labview中添加自定义动态控件(旋转风扇控件)
     前言:    使用labview做了一个自定义的labview控件在Labview中添加自定义动态控件(旋转风扇控件)下面具体介绍步骤:1.打开Labview,新建VI项目,在前面板“文件”->“新建”->“自定义控件”2.接着,转到“新建”->“自定义控件”3.然后,右击添加一个布尔......
  • 在 Wed 中应用 MyBatis(同时使用MVC架构模式,以及ThreadLocal 事务控制)
    1.在Wed中应用MyBatis(同时使用MVC架构模式,以及ThreadLocal事务控制)@目录1.在Wed中应用MyBatis(同时使用MVC架构模式,以及ThreadLocal事务控制)2.实现步骤:1.第一步:环境搭建2.第二步:前端页面index.html3.第三步:创建pojo包、service包、dao包、web包、utils包,exceptions......
  • 使用自定义查询参数获取 fullcalendar api
    我正试图配置fullcalendar5从数据库中获取api。除了开始和结束之外,我还想向请求传递额外的查询参数。我已经尝试过这种方法,但发现请求总是忽略附加参数。events:{url:'http://localhost:4000/api/timesheet'、type:'GET'、......
  • Glif – 基于 SD 的 AI 绘画浏览器插件
    基于StableDiffusion的AI绘画浏览器插件,你可以在网页上对任何图片根据文本描述来重新生成新的图片样式。例如有些图片素材有版权现在,那么这个时候你就可以用AI来重新生成无版权限制的。功能介绍所有工作流程均由glif.app提供支持,然后使用GPT-4等LLM进行扩展,并......
  • spring和Mybatis的逆向工程
    目录十二、注解开发1、注解方式单表的增删改查的操作十三、逆向工程13.1、创建逆向工程的步骤⑴添加依赖⑵配置MyBatis的核心配置文件⑶创建逆向工程的配置文件,该文件文件名必须是:generatorConfig.xml13.2测试十四、分页插件14.1、使用步骤14.2分页插件的使用十二、注解开发注......
  • 掌握 JMeter 插件管理器:提升性能测试的利器
    前言ApacheJMeter是一款强大的性能测试工具,其灵活性和扩展性使其在性能测试领域广受欢迎。JMeter插件管理器(JMeterPluginsManager)为用户提供了一个方便的平台来安装、更新和管理各种插件,从而大大扩展了JMeter的功能。本文将详细介绍如何使用JMeter插件管理器,包括安装、......