MyBatis 分页插件

前言

日常开发中,我们经常会碰上需要列表查询的场景,如果查询结果列表过长,则需要对其进行分页。MyBatis 其实自带分页功能,通过一个RowBounds的类实现,但是存在一个非常严重的问题,那就是它会一条SQL中查询所有的结果出来,然后根据从第几条到第几天取出数据返回。如果这条SQL返回很多数据,系统很可能内存溢出。

分页插件是 MyBatis 中最为经典和常用的插件,本文就来介绍一种分页插件。

MyBatis 的四大对象

之前的文章中讲过,Mapper 映射是通过动态代理实现的,而 Mapper 的执行过程是通过 Executor、StatementHandler、ParameterHandler 和 ResultHandler 来完成数据库操作和结果返回的。

总的来讲、Executor 会先调用 StatementHandler 的 prepare() 方法预编译 SQL 语句,同时设置一些基本的运行参数。然后调用 parameterize() 方法启用 ParameterHandler 设置参数,完成预编译,跟着就是执行查询,如果需要查询,就用 ResultSetHandler 封装结果返回给调用者。

自定义分页插件

MySQL 提供了 limit m,n 分页语句,但是如果为每一个分页查询都添加上 limit m,n 的分页语句,未免过于麻烦,这里有一种一劳永逸的思路。考虑到 MyBatis 通过 StatementHandler 的 prepare() 方法预编译SQL,如果此时在预编译的 sql 语句后面加上 limit m,n 分页语句,便能达到分页的效果了。

我们可以自定义一个拦截器,用来拦截 StatementHandler 的 prepare() 方法,代码如下:

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

    //每页显示的条目数
    private int pageSize;
    //当前现实的页数
    private int currPage;

    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        MetaObject metaObjectHandler = SystemMetaObject.forObject(statementHandler);

        while(metaObjectHandler.hasGetter("h")){
            Object o = metaObjectHandler.getValue("h");
            metaObjectHandler = SystemMetaObject.forObject(o);
        }

        while (metaObjectHandler.hasGetter("target")){
            Object o = metaObjectHandler.getValue("target");
            metaObjectHandler = SystemMetaObject.forObject(o);
        }

        MappedStatement mappedStatement = (MappedStatement)metaObjectHandler.getValue("delegate.mappedStatement");
        String mapId = mappedStatement.getId();

        //此处约定所有以ByPage结果的查询方法都使用分页
        if(mapId.matches(".+ByPage$")){
            ParameterHandler parameterHandler = (ParameterHandler)metaObjectHandler.getValue("delegate.parameterHandler");
            Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();

            this.pageSize = paraObject.get("pageSize") == null?this.pageSize:(Integer)paraObject.get("pageSize");
            this.currPage = (Integer) paraObject.get("currPage");

            String sql = (String) metaObjectHandler.getValue("delegate.boundSql.sql");

            String limitSql = sql.trim() + " limit " + (currPage - 1) * pageSize + "," + pageSize;

            metaObjectHandler.setValue("delegate.boundSql.sql", limitSql);
        }

        return invocation.proceed();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    public void setProperties(Properties properties) {
        //默认每页返回结果数量为10
        String limit1 = properties.getProperty("limit", "10");
        this.pageSize = Integer.valueOf(limit1);
    }
}

拦截器有了,需要让 MyBatis 知道这个拦截器的存在,因此需要在 configuration 中添加 plugin标签,

<configuration>
……
    <plugins>
        <plugin interceptor="com.maowei.learning.orm.MyPageInterceptor">
            <property name="pageSize" value="10"/>
        </plugin>
    </plugins>
……
</configuration>

此外还要添加 mapper 方法和 SQL 语句。

List<User> queryUserByPage(Map<String,Object> data);

<select id="queryUserByPage" parameterType="map" resultType="User" flushCache="true">
        select * from user
</select>

这里需要注意的是 flushCache 的配置,由于我们是在中间过程偷天换日换掉了SQL,但是MyBatis并不知道,所以在SESSION级别的一级缓存开启的前提下,如果每次不刷新缓存,查询结果始终为第一次查询的结果。

运行一下我们的插件,看一下查询的过程和结果。

Map<String,Object> map = new HashMap<String, Object>();
int i = 0;
int size = 0;

do{
    System.out.println("当前显示结果第"+(++i)+"页:");
    map.put("pageSize",5);
    map.put("currPage",i);

    List<User> list = dao1.queryUserByPage(map);

    for(User u : list){
        System.out.println(u.toString());
    }

    System.out.println("======================");

    size = list.size();
}while (size == 5);

这里写图片描述

总结

本文介绍了一种比较简单的分页插件,当然还有很多地方需要完善,希望能给读者在实现分页功能时提供一种思路。

猜你喜欢

转载自blog.csdn.net/tjreal/article/details/80428217