Mybatis 插件

1. 插件简介

一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作。以 MyBatis 为例,我们可基于 MyBatis 插件机制实现分页,分表,监控等功能。由于插件和业务无关,业务也无法感知插件的存在。因此可以无感植入插件,在无形中增强功能。

2. Mybatis 插件介绍

Mybatis 作为一个应用广泛的优秀的 ORM 开源框架,这个框架具有强大的灵活性,在四大组件( Executor、StatementHandler、ParameterHandler、ResultSetHandler )处提供了简单易用的插件扩展机制。Mybatis 对持久层的操作就是借助于四大核心对象。MyBatis ⽀持用插件对四大核心对象进行拦截,对 MyBatis 来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis 中的四大对象都是代理对象。
在这里插入图片描述

MyBatis 所允许拦截的方法如下:

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

3. Mybatis 插件原理

在四大对象创建的时候

  • 每个创建出来的对象不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler)。
  • 获取到所有的 Interceptor (拦截器:插件需要实现的接口);调用 interceptor.plugin(target);返回target 包装后的对象。
  • 插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP (面向切面)我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行。

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

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}
/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.plugin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author Clinton Begin
 */
public class InterceptorChain {

    private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

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

    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }

}

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

如果我们想要拦截 Executor 的 query 方法,那么可以这样定义插件:

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

除此之外,我们还需将插件配置到 sqlMapConfig.xml 中。

<plugins>
  	<plugin interceptor="com.study.plugin.ExamplePlugin"/>
</plugins>

这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。待准备工作做完后,MyBatis 处于就绪状态。我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创建 SqlSession。

Executor 实例会在创建 SqlSession 的过程中被创建,Executor 实例创建完毕后,MyBatis 会通过 JDK 动态代理为实例生成代理类。这样,插件逻辑即可在 Executor 相关方法被调用前执行。

以上就是MyBatis插件机制的基本原理。

4. 自定义插件

4.1. 插件接口

Mybatis 插件接口 Interceptor

  • Intercept 方法,插件的核心方法
  • plugin 方法,生成 target 的代理对象
  • setProperties 方法,传递插件所需参数

4.2. 自定义插件

设计实现一个自定义插件

package com.study.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;

@Intercepts(
    @Signature(type = StatementHandler.class,
               method = "prepare",
               args = {Connection.class,Integer.class})
)
public class MyPlugin implements Interceptor {

    // 拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强....");
        //原方法执行
        return invocation.proceed();
    }

    // 主要为了把当前的拦截器生成代理存到拦截器链中
    @Override
    public Object plugin(Object o) {
        Object wrap = Plugin.wrap(o, this);
        return wrap;
    }

    // 获取配置文件的参数
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件的参数是:"+properties);
    }
}

sqlMapConfig.xml

<plugins>
  <plugin interceptor="com.study.plugin.MyPlugin">
    <property name="name" value="tom"/>
  </plugin>
</plugins>

mapper 接口

package com.study.mapper;

import com.study.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserMapper {

    //查询用户
    @Select("select * from user")
    @Options(useCache = false)
    List<User> selectUser();

}

测试类

@Test
public void test() {
    List<User> userList = userMapper.selectUser();
    for (User user : userList) {
        System.out.println(user);
    } 
}

5. 源码分析

执行插件逻辑
Plugin 实现了 InvocationHandler 接口,因此它的 invoke 方法会拦截所有的方法调用。invoke 方法会对所拦截的方法进行检测,以决定是否执行插件逻辑。该方法的逻辑如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 获取被拦截方法列表,比如:
        // signatureMap.get(Executor.class), 可能返回 [query, update, commit]
        Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
        //检测方法列表是否包含被拦截的方法
        return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) /*执⾏插件逻辑*/ : method.invoke(this.target, args);//执⾏被拦截的方法
    } catch (Exception var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}

invoke 方法的代码比较少,逻辑不难理解。首先 ,invoke 方法会检测被拦截方法是否配置在插件的 @Signature 注解中,若是,则执行插件逻辑,否则执行被拦截方法。插件逻辑封装在 intercept 中,该方法的参数类型为 Invocation Invocation ;主要用于存储目标类,方法以及方法参数列表。下面简单看 一下该类的定义

package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author Clinton Begin
 */
public class Invocation {

    private final Object target;
    private final Method method;
    private final Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }

    public Object getTarget() {
        return target;
    }

    public Method getMethod() {
        return method;
    }

    public Object[] getArgs() {
        return args;
    }

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }

}

关于插件的执行逻辑就分析结束

6. pageHelper 分页插件

MyBatis 可以使用第三方的插件来对功能进行扩展,分页助手 PageHelper 是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

开发步骤:

  • 导入通用 PageHelper 的坐标
  • 在 mybatis 核心配置文件中配置 PageHelper 插件
  • 测试分页数据获取

导入通用 PageHelper 坐标

<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>3.7.5</version>
</dependency>
<dependency>
  <groupId>com.github.jsqlparser</groupId>
  <artifactId>jsqlparser</artifactId>
  <version>0.9.1</version>
</dependency>

在 mybatis 核心配置文件中配置 PageHelper 插件

<plugins>
  <!--注意:分⻚助手的插件 配置在通用馆mapper之前 -->
  <plugin interceptor="com.github.pagehelper.PageHelper">
    <!-- 指定方⾔ -->
    <property name="dialect" value="mysql"/>
  </plugin>
</plugins>

测试分页代码实现

@Test
public void pageHelperTest(){

    PageHelper.startPage(1,1);
    List<User> users = userMapper.selectUser();
    for (User user : users) {
        System.out.println(user);
    }

    PageInfo<User> pageInfo = new PageInfo<>(users);
    System.out.println("总条数:"+pageInfo.getTotal());
    System.out.println("总页数:"+pageInfo.getPages());
    System.out.println("当前页:"+pageInfo.getPageNum());
    System.out.println("每页显示的条数:"+pageInfo.getPageSize());
    System.out.println("是否第⼀⻚:" + pageInfo.isIsFirstPage());
    System.out.println("是否最后⼀⻚:" + pageInfo.isIsLastPage());
}

7. 通用 mapper

什么是通用 Mapper?
通用 Mapper 就是为了解决单表增删改查,基于 Mybatis 的插件机制。开发人员不需要编写 SQL,不需要在 DAO 中增加方法,只要写好实体类,就能支持相应的增删改查方法。

如何使用
首先在 maven 项目,在 pom.xml 中引入 mapper 的依赖

<dependency>
  	<groupId>tk.mybatis</groupId>
  	<artifactId>mapper</artifactId>
  	<version>3.1.2</version>
</dependency>

Mybatis 配置文件中完成配置

<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
  	<!-- 通用 Mapper 接口,多个通用接口用逗号隔开 -->
  	<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
</plugin>

实体类设置主键

package com.study.pojo;

import lombok.Data;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

@Data
@Table(name = "user")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String username;
    
}

定义通用 mapper

package com.study.mapper;

import com.study.pojo.User;
import tk.mybatis.mapper.common.Mapper;

public interface IUserMapper extends Mapper<User> {

}

测试

@Test
public void mapperTest() throws IOException {
	InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
	SqlSession sqlSession = sqlSessionFactory.openSession();
	IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
	User user = new User();
	user.setId(1);
	User user1 = mapper.selectOne(user);
	System.out.println(user1);
	
	
	//2.example方法
	Example example = new Example(User.class);
	example.createCriteria().andEqualTo("id",1);
	List<User> users = mapper.selectByExample(example);
	for (User user2 : users) {
	    System.out.println(user2);
	}
}
<think>好的,用户需要寻找与MyBatis插件相关的资料或教程,特别是开发和使用方面的内容。现在需要根据提供的引用内容来整理回答。首先,我需要回顾用户提供的四个引用,这些引用都提到了不同的MyBatis插件,比如MyBatis Generator、MyBatisX等,以及它们的安装和使用方法。 首先,确定用户的需求是寻找教程和资料,所以回答需要涵盖插件的介绍、安装步骤、核心功能以及使用步骤。然后,根据每个引用的内容提取关键点。例如,引用1提到MyBatis Generator的启动文件和目录结构,引用2和3详细说明了MyBatisX插件的安装步骤和功能,引用4则补充了MyBatisX的作用。 接下来,需要将这些信息整合成一个结构清晰的回答。可能需要分为几个部分:插件简介、安装教程、核心功能、使用步骤以及相关资源推荐。同时,要确保引用标注正确,比如在提到每个插件的功能时,对应到正确的引用来源。 另外,用户可能对如何开发自己的插件感兴趣,但提供的引用中没有详细说明开发过程,所以可能需要建议参考官方文档或社区资源。同时,生成的相关问题要围绕MyBatis插件的使用、对比、选择以及开发,确保问题贴合用户的潜在需求。 需要检查每个步骤是否符合用户的要求,比如使用正确的中文表达,LaTeX格式是否正确应用(虽然当前问题可能不涉及数学公式,但如果有的话需要处理),以及是否自然添加引用标识。最后,确保回答真实可靠,基于提供的引用内容,不添加未提及的信息。</think>以下是与MyBatis插件相关的教程和核心信息整理: ### 一、MyBatis插件分类与用途 1. **MyBatis Generator插件** 用于自动生成MyBatis的Mapper接口、XML映射文件和实体类。其核心启动文件为`ExampleTargetPlugin.java`,位于`src/main/java/com/itfsw/mybatis/generator/plugins/`目录下[^1]。 2. **MyBatisX插件(IDEA集成)** 提供代码生成、智能补全、语法高亮、数据库操作等功能,支持MyBatisMyBatis-Plus开发[^2][^4]。 --- ### 二、插件安装教程 #### MyBatisX安装步骤(IntelliJ IDEA) 1. 打开IDEA,进入`File > Settings > Plugins` 2. 在插件市场搜索"MyBatisX" 3. 点击`Install`安装,重启IDEA生效[^2][^3] #### MyBatis Generator配置 需在项目的`pom.xml`中添加MyBatis-Plus依赖(以Spring Boot项目为例): ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>最新版本号</version> </dependency> ``` --- ### 三、核心功能对比 | 插件名称 | 核心能力 | 优势场景 | |------------------|--------------------------------------------------------------------------|------------------------| | MyBatis Generator| 自动生成基础CRUD代码 | 快速搭建项目框架 | | MyBatisX | 代码生成、SQL跳转、数据库管理、文档生成 | 开发效率提升与代码维护 | --- ### 四、MyBatisX使用步骤 1. **代码生成** - 右键项目中的表,选择`MyBatisX-Generator` - 配置生成路径和包名,自动生成Mapper/Service/Entity代码 2. **数据库操作** - 在IDEA内直接执行SQL语句 - 支持可视化数据表浏览与编辑 3. **导航与校验** - 通过`XML <-> Java`双向跳转快速定位代码 - 实时语法错误检查 --- ### 五、相关资源推荐 1. **官方文档** - [MyBatis Generator官方指南](https://mybatis.org/generator/) - [MyBatisX插件GitHub仓库](https://github.com/baomidou/mybatis-plus) 2. **社区教程** - MyBatisX插件详细使用案例(含动图演示)[^2] - 插件开发进阶:自定义模板方法
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小松の博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值