Mybatis 扩展

474 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、Mybatis 的插件机制

1.1 插件简介

image-20220610102918834.png

Mybatis 通过四大核心组件,对开发者友好提供了易于扩展的插件机制。对 Mybatis 来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的。

  • Executor:执行器。处理update/query/commit/rollback等方法
  • StatementHandler:SQL 语法构建器。处理prepare/parameterize/batch/update/query等方法
  • ParameterHandler:参数处理器。处理getParameterObject/setParameters等方法
  • ResultSetHandler:结果集处理器。处理handleResultSets/handleOutputParameters等方法

1.2 插件使用

  1. 实现接口Interceptor

    /**
     * 文件描述
     *
     * @Project yiwenup-sample
     * @Package cloud.yiwenup.sample.mybatis.plugin
     * @Author yiwenup
     * @Date 2022-06-10 10:46:01
     * @Description
     */
    @Intercepts({
            // 此处需要配置待拦截的组件信息,method 的方法名称以及 args 参数信息都需要准确,可以参考 StatementHandler 源码配置
            @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();
        }
    ​
        /**
         * 为拦截器生成一个代理,放入拦截器链中
         */
        @Override
        public Object plugin(Object target) {
            System.out.println("@@@@@@@@@@@@@@@@@@@将要包装的目标对象:" + target);
            return Plugin.wrap(target, this);
        }
    ​
        /**
         * 插件初始化的时候会被首先调用,仅调用一次,方便将外部配置引入
         */
        @Override
        public void setProperties(Properties properties) {
            System.out.println("@@@@@@@@@@@@@@@@@@@插件配置的初始化参数:" + properties);
        }
    }
    
  2. 全局配置文件中注册拦截器

    <plugins>
        <plugin interceptor="cloud.yiwenup.sample.mybatis.plugin.MyPlugin">
            <property name="username" value="zhangsan"/>
        </plugin>
    </plugins>
    
  3. 测试结果

    image-20220610110352704.png

1.3 插件原理

  • 插件核心的位置是实现intercept方法,在此处可以定制增强内容,另外的关键点是plugin方法,这个方法中的默认实现是调用了Plugin.wrap(target, intercept)
  • Plugin.wrap(target, intercept)的底层静态实现中,首先会校验插件MyPlugin的类注解,看看是不是声明了@Intercepts/@Signature,组装待拦截信息为一个Map<Class<?>, Set<Method>>,其中 Map 的 key 便是四大组件之一,Map 的 value 是其中的方法
  • 获取到Map<Class<?>, Set<Method>>之后,Plugin.wrap(target, intercept)拿出target参数的类信息,判断当前待代理的对象是否是在 Map<Class<?>, Set<Method>>中,如果不在,则直接放行
  • 如果target是在待拦截对象中,那么就使用JDK动态代理为其生成代理对象,最终执行到InvocationHandler#invoke
  • Plugin#invoke中,首先还是判断当前的方式是否是Map<Class<?>, Set<Method>>中的待增强方法,如果是,则执行MyPlugin#intercept,否则进行放行处理

1.4 插件机制的使用场景

  • PageHelper:分页插件
  • tk.mybatis:基于 Mybatis 封装的增强通用 Mapper

二、Mybatis 架构原理

image-20220610143012243.png

2.1 Mybatis 主要构件及相互关系

  • SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  • Executor:MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler:封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将 Statement 结果集转换成 List集合
  • ParameterHandler:负责对用户传递的参数转换成 JDBC Statement 所需要的参数。主要是参数映射配置、参数映射解析、参数类型解析
  • ResultSetHandler:负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合
  • TypeHandler:负责 java 数据类型和 jdbc 数据类型之间的映射和转换
  • MappedStatement:维护了一条 <select | update | delete | insert> 节点的封装
  • SqlSource:负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回
  • BoundSql:表示动态生成的 SQL 语句以及相应的参数信息

2.2 流程解析

  1. 加载配置文件,或者解析注解参数,封装Configuration对象
  2. 使用端调用上层 API,通过SqlSession开启会话发送数据请求
  3. 框架层接收到请求,根据statementId查询MappedStatement
  4. 根据MappedStatement对象获取最终要执行的 SQL 和传入的参数
  5. 获取数据库连接,访问数据库,执行 SQL 并拿到执行结果
  6. 根据MappedStatement对象中的结果集映射配置对得到的执行结果进行转换,最终得到处理结果
  7. 释放资源并返回处理结果

三、Mybatis 周边

  • PageHelper
  • Mybatis-Plus
  • tk.mybatis