PageHelper执行流程及自定义MyBatis插件实现

PageHelper 通过集成 MyBatis 的 Interceptor 接口,来实现分页插件的功能。

首先要使用 PageHelper ,需要的 pom.xml 文件中引入 PageHelper 的依赖:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.8</version>
</dependency>

然后,在 MyBatis 中配置 PageHelper 插件:

<?xml version = "1.0" encoding = "UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0 //EN"
        "http//mybatis.org/dtd/mybatis-3-config.dtd" >
<configuration>
    <settings>
		...
    </settings>
    
    <typeAliases>
        ...
    </typeAliases>
    // 配置插件
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

    <environments default="development">
		...
    </environments>

    <databaseIdProvider type="VENDOR">
		...
    </databaseIdProvider>
    <mappers>
		...
    </mappers>


</configuration>

由于我们在这里配置了插件的实现类 com.github.pagehelper.PageInterceptor,所以,在初始化 MyBatis 配置时,会在初始化完执行器后,对执行器对象通过 PageInterceptor 在进行一层动态代理,源码如下:

Configuration.class

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 初始化执行器
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 对执行器进行动态代理
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }



InterceptorChain.class
// 这里的 interceptor 即为我们在 mybatis-config.xml 中配置的 plugin 的实现
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

PageInterceptor.class
// 调用 MyBatis 的 Plugin.wrap(...) ,用 this 对象来对 taget 进行动态代理,返回一个由 PageHelper 代理的对象
// @Param target 即 executor
@Override
public Object plugin(Object target) {
    return Plugin.wrap(target, this);
}

PageHelper执行流程及自定义MyBatis插件实现_第1张图片
上图中可以看出, PageHelper 分页功能的实现,主要的流程可以分为:

在 mybatis-config.xml 中配置插件

通过 MyBatis 的 Plugin 中的 wrap(…) 方法生成由 Plugin 代理的 executor 对象

当调用 executor 进行增删改操作时,会被转发到 Plugin 的 invoke(…) 方法;该方法内部会调用拦截器的 interceptor(…) ,在该方法内,会先进行一次 count 查询,如果返回条数大于 0 ,则进行分页查询,等于 0 则不进行分页查询,直接返回。

Plugin.class

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method)) {
        	// 当前方法在被拦截的方法之列,则调用 PageInterceptor 进行拦截
        	return interceptor.intercept(new Invocation(target, method, args));
    	}
    	// 不在则放行
    	return method.invoke(target, args);
    } catch (Exception e) {
    	throw ExceptionUtil.unwrapThrowable(e);
    }
}

PageInterceptor.class
    
@Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            ...
            // 进行 count 查询
            Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                //判断查询结果是否有数据
                if (!dialect.afterCount(count, parameter, rowBounds)) {
                    //当查询总数为 0 时,直接返回空的结果
                    return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                }
            ...
            // 执行 limit 查询
    		resultList = ExecutorUtil.pageQuery(dialect, executor,
                                        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            ...
            
    }

PageHelper 的分页功能大致的实现就是这样,下面来自己实现一个插件,功能是在查询前打印 SQL 语句。
首先,创建一个自己的拦截器类:

MyBatisInterceptor.class

// 只拦截两个 query 方法
@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
public class MyBatisInterceptor implements Interceptor {
// 分别重写 Interceptor 接口的三个方法
    
// 拦截器方法
@Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        Executor executor = (Executor) invocation.getTarget();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        CacheKey cacheKey;
        BoundSql boundSql;
        // 由于我们这里拦截的是 executor 中的两个 query 方法,这两个方法唯一的区别是参数个数的不同,所以,我们这里通过参数个数的不同来获取具体的 SQL 语句
        if (args.length == 4) {
            //4 个参数时
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
		// 打印 执行 SQL
        System.err.println("执行查询前打印查询 SQL : " + boundSql.getSql());
        List<Object> resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        return resultList;
    }
    
    
// 生成动态代理对象,使用当前对象作为拦截器,被拦截对象为 target 对象,也就是 executor 对象    
@Override
    public Object plugin(Object target) {
        Object wrap = Plugin.wrap(target, this);
        return wrap;
    }
    
// 拦截器的一些参数设置,我这里没有,就没有实现
@Override
    public void setProperties(Properties properties) {

    }
    
    
}

然后,将 mybatis-config.xml 中的 plugin 标签的 interceptor 属性设置为我们这个类的类路径。

<plugins>
	<plugin interceptor="com.mybatis.simple.interceptor.MyBatisInterceptor"></plugin>
</plugins>

然后执行测试方法,测试方法如下:

@Test
    public void testMyPlugin() {
        SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
        SysUser user = mapper.selectById(1004);
        Assert.assertNotNull(user);
    }

便可以在控制开看到打印如下结果:

// 拦截器的 interceptor 方法打印
执行查询前打印查询 SQL : select * from sys_user where id = ?
DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1004(Integer)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1004, 孙悟空, 123456, SWK@xiyouji.com, <<BLOB>>, <<BLOB>>, 2020-06-15 06:53:56
DEBUG [main] - <==      Total: 1

你可能感兴趣的:(Mybatis入门)