mybatis拦截器学习-手写一个分页插件

本文详细介绍了MyBatis拦截器的工作原理,重点讲解如何编写一个分页插件。通过拦截StatementHandler,重写intercept、plugin、setProperties方法,对以ByPage结尾的查询方法进行拦截,利用MetaObject处理对象属性。最后,展示了在SpringBoot项目中配置分页插件的步骤,并提供测试用例验证分页效果。
摘要由CSDN通过智能技术生成

一、我们先来看一下mybatis的执行流程。从图中可以看出在Excutor之前都是在加载配置,创建SqlSeesion等工作.

在这里插入图片描述

所以mybatis允许拦截的地方只有四处:

1、Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2、ParameterHandler (getParameterObject, setParameters)
3、ResultSetHandler (handleResultSets, handleOutputParameters)
4、StatementHandler (prepare, parameterize, batch, update, query)

总体概括为:

1、拦截执行器的方法
2、拦截参数的处理
3、拦截结果集的处理
4、拦截Sql语法构建的处理

二、在我们插件实现中会用到MetaObject类,我们先简单的讲一下这个类,这是mybatis提供的一个的工具类,通过它包装一个对象后可以获取或设置该对象的原本不可访问的属性(比如那些私有属性,通过反射的方法)。它有个三个重要方法经常用到:

* MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) 用于包装对象;

* Object getValue(String name) 用于获取属性的值(支持OGNL的方法);

* void setValue(String name, Object value) 用于设置属性的值(支持OGNL的方法);

举个简单的例子

//java bean
Example javaBean = new Example();
javaBean.setContent("hello");

MetaObject javaBeanMeta = MetaObject.forObject(javaBean,
                DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
System.out.println(javaBeanMeta.getValue("content"));
javaBeanMeta.setValue("content", "world");
System.out.println(javaBeanMeta.getValue("content"));

我们通过MetaObject就可以获得或者给对象的私有属性赋值。MetaObject还有一些其他的功能我们先不深究。

三、实现mybatis分页插件

1、设置拦截器类型

因为我们是对sql进行重新拼装,所以这了我们对StatementHandler进行拦截(上面说到的:拦截Sql语法构建的处理)

2、重写intercept、plugin、setProperties方法
3、把处理后的sql赋值给Boundsql.
4、在配置中添加拦截器配置
我们在这里做一个设定,如果查询方法以ByPage结尾的,我们才对他进行拦截分页处理。

四、具体代码PageIntercept

package com.lw.study.mybatisIntercepter;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

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

/**
 * @author 
 * @date 14:21 2019/7/1
 * 写一个mybatis拦截器需要实现Interceptor接口,同时类上面加上Interceptors注解,
 * Signature标签中申明你要拦截的handler,我们这里是StatementHandler,以及你要拦截的方法,args是方法的参数类型
 */
@Intercepts({
        @Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})
})
public class PageIntercept implements Interceptor{
    private String dbType;
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //获取StatementHandler
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //我们这里使用的SystemMetaObject是mybatis帮我们封装好了的,点进去看就知道,是一个默认的MetaObject构造方法。可以省去我们的一些操作
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环
        // 可以分离出最原始的的目标类)
        while (metaObject.hasGetter("h")) {
            Object object = metaObject.getValue("h");
            System.out.println(object.toString());
            metaObject = SystemMetaObject.forObject(object);

        }
        // 分离最后一个代理对象的目标类
        while (metaObject.hasGetter("target")) {
            Object object = metaObject.getValue("target");
            System.out.println(object.toString());
            metaObject = SystemMetaObject.forObject(object);

        }
        //获取查询接口映射
        // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的
        //  MappedStatement的sql
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        String mapId = mappedStatement.getId();
        //正则匹配.ByPage结尾的请求。分页统一实现
        if(mapId.matches(".+ByPage$")){
            //管理参数的handler
            ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");
            //获取请求时的参数,这里我们查询sql使用的是map来封装查询参数,当然,优雅一点我们可以创建一个对象来封装。
           Map<String,Object> map = (Map<String, Object>) parameterHandler.getParameterObject();
           //当前页数
            int currPage = (int) map.get("currPage");
            //每页条数
            int pageSize = (int) map.get("pageSize");
            //原始sql
            String originalSql = statementHandler.getBoundSql().getSql().trim();
            System.out.println("获取原始sql:"+ originalSql);
            if (originalSql.endsWith(";")) {
            //去掉原始sql末尾的分号,以便我们对其进行拼接。
                originalSql = originalSql.substring(0, originalSql.length() - 1);
            }
            //注意拼接时limit左右两边的空格,不然就是连在一起了,会有语法错误。
            String limitSql = originalSql + " limit " + (currPage - 1) * pageSize + "," + pageSize;
            //将拼装后的sql复制给BoundSQL
            metaObject.setValue("delegate.boundSql.sql",limitSql);

        }
        //最后将处理权限返回给mybatis,让它继续执行。
                return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {


    }

}

最后别忘了加上我们的拦截器配置

如果你是使用的xml配置。就在sqlSessionFactoryBean配置里加上

 <plugins>
    <plugin interceptor="com.xxx.xxx.utils.MyBatisPagePlugin">
    </plugin>
  </plugins>

我这里使用的是springboot搭建的项目,直接在代码里配置了,主要是加了写注释的那一行。

@Configuration
@EnableTransactionManagement
public class MyBatisConfig {

  @Bean
  @ConfigurationProperties(prefix = "spring.datasource")
  public DataSource dataSource(){
      return new org.apache.tomcat.jdbc.pool.DataSource();
  }

  @Bean(name = "sqlSessionFactory")
  public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
      SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      sqlSessionFactoryBean.setDataSource(dataSource());

      PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

      sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/sqlmap/*.xml"));
      //在这里加上我们的插件配置。
      sqlSessionFactoryBean.setPlugins(new Interceptor[]{new PageIntercept()});

      return sqlSessionFactoryBean.getObject();
  }

}

来个测试类测试一下,查出来前五条数据。

 @Test
  public void testUser() {
      Map<String, Object> map = new HashMap<>();
      map.put("idStart", 1000);
      map.put("idEnd", 2000);
      map.put("currPage", 1);
      map.put("pageSize", 5);
      //根据id范围查询user信息
      List<User> list = userExtMapper.getAllUserByPage(map);
      System.out.println(list.toString());
  }
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值