MyBatis插件原理探究和自定义插件实现

这篇具有很好参考价值的文章主要介绍了MyBatis插件原理探究和自定义插件实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

插件简介

⼀般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者⾃⾏拓展。这样的好处是显⽽易⻅的,⼀是增加了框架的灵活性。⼆是开发者可以结合实际需求,对框架进⾏拓展,使其能够更好的⼯作。以MyBatis为例,我们可基于MyBati s插件机制实现分⻚、分表,监控等功能。由于插件和业务⽆关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能。

Mybatis插件介绍

Mybati s作为⼀个应⽤⼴泛的优秀的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等⽅法);

Mybatis插件原理

在四⼤对象创建的时候

  1. 每个创建出来的对象不是直接返回的,⽽是interceptorChain.pluginAll(parameterHandler);
  2. 获取到所有的Interceptor (拦截器)(插件需要实现的接⼝);调⽤ interceptor.plugin(target);返回 target 包装后的对象
  3. 插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象;AOP (⾯向切⾯)我们的插件可以为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏;

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

public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
Object object, BoundSql sql, InterceptorChain interceptorChain){
    ParameterHandler parameterHandler =
    mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
    parameterHandler = (ParameterHandler)interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

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.zjq.plugin.ExamplePlugin"></plugin>
</plugins>

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

⾃定义插件

插件接口

Mybatis 插件接⼝-Interceptor

  • Intercept⽅法,插件的核⼼⽅法
  • plugin⽅法,⽣成target的代理对象
  • setProperties⽅法,传递插件所需参数

⾃定义插件

设计实现⼀个⾃定义插件

@Intercepts({//注意看这个⼤花括号,也就这说这⾥可以定义多个@Signature对多个地⽅拦截,都⽤这个拦截器
    @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为要拦截的对象
    * @Return代理对象
    */
    @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 );
    }
}

sqlMapConfig.xml
MyBatis插件原理探究和自定义插件实现,?主流框架,# Mybatis&amp;Mybatis-Plus,mybatis,SSM,pageHelper,tkMapper,原力计划

mapper接⼝
MyBatis插件原理探究和自定义插件实现,?主流框架,# Mybatis&amp;Mybatis-Plus,mybatis,SSM,pageHelper,tkMapper,原力计划

mapper.xml

<mapper namespace="com.zjq.mapper.UserMapper">

    <!--sql语句抽取-->
    <sql id="selectUser">
      select * from user
    </sql>

    <select id="findByCondition" parameterType="user" resultType="user">
        <include refid="selectUser"></include>
        <where>
            <if test="id!=0">
                and id=#{id}
            </if>
            <if test="username!=null and username!=''">
                and username=#{username}
            </if>
            <if test="password!=null and password!=''">
                and password=#{password}
            </if>
        </where>
    </select>
  </mapper>

测试类

public class PluginTest {
    @Test
    public void test() throws IOException {
        InputStream resourceAsStream =
        Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User condition = new User();
        //condition.setId(1);
        condition.setUsername("zjq");
        List<User> byPaging = userMapper.findByCondition(condition);
        for (User user : byPaging) {
            System.out.println(user);
        }
    }
}

源码分析

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

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

invoke⽅法的代码⽐较少,逻辑不难理解。⾸先,invoke⽅法会检测被拦截⽅法是否配置在插件的@Signature注解中,若是,则执⾏插件逻辑,否则执⾏被拦截⽅法。插件逻辑封装在intercept中,该⽅法的参数类型为Invocationo Invocation主要⽤于存储⽬标类,⽅法以及⽅法参数列表。下⾯简单看⼀下该类的定义

public class Invocation {
    private final Object target;
    private final Method method;
    private final Object[] args;
    public Invocation(Object targetf Method method, Object[] args) {
    this.target = target;
    this.method = method;
    //省略部分代码
    public Object proceed() throws InvocationTargetException,
    IllegalAccessException { //调⽤被拦截的⽅法

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

pageHelper分页插件

MyBatis可以使⽤第三⽅的插件来对功能进⾏扩展,分⻚助⼿PageHelper是将分⻚的复杂操作进⾏封装,使⽤简单的⽅式即可获得分⻚的相关数据
开发步骤:

  1. 导⼊通⽤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>
  1. 在mybatis核⼼配置⽂件中配置PageHelper插件
<!--注意:分⻚助⼿的插件 配置在通⽤馆mapper之前*-->*
<plugin interceptor="com.github.pagehelper.PageHelper">
  <!—指定⽅⾔ >
  <property name="dialect" value="mysql"/>
</plugin>
  1. 测试分⻚代码实现
@Test
public void testPageHelper() {
    //设置分⻚参数
    PageHelper.startPage(1, 2);
    User condition = new User();
    //condition.setId(1);
    condition.setUsername("zjq");
    List<User> select = userMapper.findByCondition(condition);
    for (User user : select) {
        System.out.println(user);
    }
}

获得分⻚相关的其他参数

//其他分⻚的数据
PageInfo<User> pageInfo = new PageInfo<User>(select);
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());

通⽤ mapper

什么是通⽤Mapper

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

如何使⽤

  1. ⾸先在maven项⽬,在pom.xml中引⼊mapper的依赖
<dependency>
  <groupId>tk.mybatis</groupId>
  <artifactId>mapper</artifactId>
  <version>3.1.2</version>
</dependency>
  1. Mybatis配置⽂件中完成配置
<plugins>
  <!--分⻚插件:如果有分⻚插件,要排在通⽤mapper之前-->
  <plugin interceptor="com.github.pagehelper.PageHelper">
    <property name="dialect" value="mysql"/>
  </plugin>
  <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
    <!-- 通⽤Mapper接⼝,多个通⽤接⼝⽤逗号隔开 -->
    <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
  </plugin>
</plugins>
  1. 实体类设置主键
@Table(name = "t_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
}
  1. 定义通⽤mapper
import com.zjq.domain.User;
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper<User> {
}
  1. 测试
@Test
public void test1() throws IOException {
    Inputstream resourceAsStream =
    Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory build = new
    SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = build.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setId(4);
    //(1)mapper基础接⼝
    //select 接⼝
    //根据实体中的属性进⾏查询,只能有一个返回值
    User user1 = userMapper.selectOne(user); 
    //查询全部结果
    List<User> users = userMapper.select(null); 
    //根据主键字段进⾏查询,⽅法参数必须包含完整的主键属性,查询条件使⽤等号
    userMapper.selectByPrimaryKey(1); 
    //根据实体中的属性查询总数,查询条件使⽤等号
    userMapper.selectCount(user); 
    
    // insert 接⼝
    //保存⼀个实体,null值也会保存,不会使⽤数据库默认值
    int insert = userMapper.insert(user);
    //保存实体,null的属性不会保存,会使⽤数据库默认值
    int i = userMapper.insertSelective(user); 
    
    // update 接⼝
    //根据主键更新实体全部字段,null值会被更新
    int i1 = userMapper.updateByPrimaryKey(user);
    
    // delete 接⼝
    //根据实体属性作为条件进⾏删除,查询条件使⽤等号
    int delete = userMapper.delete(user);
    //根据主键字段进⾏删除,⽅法参数必须包含完整的主键属性
    userMapper.deleteByPrimaryKey(1); 
    
    //(2)example⽅法
    Example example = new Example(User.class);
    example.createCriteria().andEqualTo("id", 1);
    example.createCriteria().andLike("val", "1");
    //⾃定义查询
    List<User> users1 = userMapper.selectByExample(example);
}

本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页:共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃文章来源地址https://www.toymoban.com/news/detail-610789.html

到了这里,关于MyBatis插件原理探究和自定义插件实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【手把手一起学习】(八) Altium Designer 20修改和自定义原理图标题栏

    直接对原理图标题栏属性进行修改,操作如图所示: 修改后,并不会显示,故该方法不可用: 正确的操作如下,先选择合适的模板: 然后,进行属性的修改: 此时可以看到,已经修改成功: 默认模板并不美观,可以自定义个人风格的标题栏: 复制一份模板,重命名: 在

    2024年02月06日
    浏览(37)
  • 一篇文章掌握WebService服务、工作原理、核心组件、主流框架

    目录 1、WebService定义 解决问题: 2、WebService的工作原理 2.1 实现一个完整的Web服务包括以下步骤 2.2 调用方式 3、Web Service的核心组件 3.1 XML 3.2 SOAP 3.3 WSDL 3.4 UDDI 4、主流框架 4.1 AXIS(已淘汰) 4.2 XFire 4.3 CXF 5、Soap协议详解 1.Soap协议是什么 2.认识Soap 3.结论 4.SOAP小总结 6、WSDL详解

    2024年01月18日
    浏览(46)
  • MyBatis的自定义插件

    MyBatis 可以拦截的四大组件 Executor - 执行器 StatementHandler - SQL 语句构造器 ParameterHandler - 参数处理器 ResultSetHandler - 结果集处理器 效果如下 创建四大对象的代码如下 首先在创建 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四个对象时,将插件(plugins)注入 调用 Intercep

    2024年02月07日
    浏览(33)
  • MyBatis——MyBatis插件原理

    本博文主要介绍MyBatis插件机原理,帮助大家更好的理解和学习MyBatis。 MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的方法调用包括: Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 拦截执行器的

    2024年02月10日
    浏览(35)
  • STM32系统和自定义bootloader的实现和应用

    简介 bootloader其实就是一段启动程序,它在芯片启动的时候最先被执行,可以用来做一些硬件的初始化或者用作固件热更新,当初始化完成之后跳转到对应的应用程序中去。 bootloader程序需要通过下载器烧写到芯片中,而APP则可以通过有线方式的UART、IIC、USB、SPI等总线来通过

    2023年04月11日
    浏览(28)
  • 基于JavaFX的扫雷游戏实现(五)——设置和自定义控件

      它来了它来了,最后一期终于来了。理论上该讲的全都讲完了,只剩下那个拖了好几期的自定义控件和一个比较没有存在感的设置功能没有讲。所以这次就重点介绍它们俩吧。   首先我们快速浏览下设置的实现,上图:   然后是控制器代码: SettingsController.java   

    2024年02月13日
    浏览(26)
  • MyBatis Plus 插件 动态数据源实现原理与源码讲解 (dynamic-datasource-spring-boot-starter-master)

    目录 1. 介绍 2. 基本原理 3. 源码介绍 3.1 使用 AOP 拦截,方法执行前获取到当前方法要用的数据源 3.2 实现自定义 DataSource 接口,实现 DataSource 接口的 getConnect 方法做动态处理 多数据源即一个项目中同时存在多个不同的数据库连接池。 比如 127.0.0.1:3306/test   127.0.0.1:3307/test 

    2024年02月07日
    浏览(35)
  • 若依(ruoyi)字典管理插件实现思路探究

     一个UI表单的构成,避免不了下拉框,多选框等标签,在开发这些标签时,通常会请求后台接口获取字典值进行动态渲染。定制化开发虽然实现简单,但会产生大量重复工作,解决这类问题的思路有哪些?文章对若依字典管理插件实现思路进行了探究,以此来开阔思路。 探

    2024年01月18日
    浏览(35)
  • Java实现钉钉企业内部应用机器和自定义机器人发送消息

     公司让写一个服务监控的功能,当监测到服务停止时,向钉钉群里推送报警信息。之前大概看到钉钉的开放平台的API文档,好像能群发消息的只有机器人。 钉钉开放平台目前提供三种机器人: 企业内部应用机器人 群模板机器人 自定义机器人 本来向用自己比较熟悉的自定义

    2024年02月12日
    浏览(47)
  • React UI组件库——如何快速实现antd的按需引入和自定义主题

    大家上午好呀~ 今天来学习一下React的UI组件库以及antd的使用相关的知识点。 感兴趣的小伙伴可以给个三连哦~ material-ui(国外) ant-design(国内蚂蚁金服) antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。 安装antd组件库: 默认按需引入antd组件

    2024年02月02日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包