Java面试之Mybatis框架4——Mybatis的分页

概述

MyBatis常见的分页有如下几种:

  • 使用RowBounds逻辑分页
  • 在SQL语句中使用limit分页
  • 使用自定义拦截器分页
  • 使用PageHelper插件分页

使用RowBounds逻辑分页

Mybatis可以通过传递RowBounds对象,来进行数据库数据的分页操作,然而遗憾的是,该分页操作是对ResultSet结果集进行分页,也就是人们常说的逻辑分页,而非物理分页。

所谓的物理分页是指在SQL语句中使用limit进行分页。

先说如何使用RowBounds来完成分页操作:

控制器类就是直接调用service层的接口

需要注意的是RowBounds的两个参数,其中offset指的是表示从第几条开始,limit指的是获取多少条数据

RowBounds的源码如下:

public class RowBounds {
    public static final int NO_ROW_OFFSET = 0;
    public static final int NO_ROW_LIMIT = 2147483647;
    public static final RowBounds DEFAULT = new RowBounds();
    private final int offset;
    private final int limit;

    public RowBounds() {
        this.offset = 0;
        this.limit = 2147483647;
    }

    public RowBounds(int offset, int limit) {
        this.offset = offset;
        this.limit = limit;
    }

    public int getOffset() {
        return this.offset;
    }

    public int getLimit() {
        return this.limit;
    }
}

service层没有什么需要注意的地方,将RowBounds作为参数即可

mapper层的代码如下,接口方法的参数仍然是RowBounds,但注意在mapper.xml中的SQL语句根查询所有记录的SQL语句一样,都是查询所有,如果带条件那么添加where语句即可,即在分页查询的SQL语句中没有对分页的处理

注意:Mybatis的分页是对结果集进行的分页

如果对分页原理感兴趣可以参考:Mybatis3.3.x技术内幕(十三):Mybatis之RowBounds分页原理

在SQL语句中使用limit分页

实际上,我们在分页中要动态设置分页参数,所以需要创建一个Page实体类

/**
 * 分页的相关属性和方法
 *
 * @author lck100
 */
public class Page implements Serializable {
    // 所有数据结果集
    private List list;
    // 每页显示多少条数据
    private int pageSize;
    // 页码(第几页)
    private int pageIndex;
    // 记录总条数
    private int totalRecords;

    /**
     * 计算总的页面数
     *
     * @return 返回一共有多少页
     */
    public int getTotalPages() {
        if (totalRecords % pageSize == 0) {
            // 如果记录总条数对每页显示记录数取整等于0,则表示页面数刚好分完。
            return totalRecords / pageSize;
        } else {// 如果取不尽,那么就添加一页来放剩余的记录
            return totalRecords / pageSize + 1;
        }
    }

    /**
     * 获取首页的页码
     *
     * @return 返回首页的页码,即为1
     */
    public int getFirstPageIndex() {
        return 1;
    }

    /**
     * 获取上一页的页码
     *
     * @return 返回上一页的页码,即当前页码减去1
     */
    public int getPreviousPageIndex() {
        if (pageIndex <= 1) {
            return 1;
        }
        return pageIndex - 1;
    }

    /**
     * 获取下一页的页码
     *
     * @return 返回下一页的页码,即当前页码加上1
     */
    public int getNextPageIndex() {
        if (pageIndex >= getLastPageIndex()) {
            return getLastPageIndex();
        }
        return pageIndex + 1;
    }

    /**
     * 获取最后一页的页码
     *
     * @return 返回最后一页的页码,即总页数
     */
    public int getLastPageIndex() {
        return getTotalPages();
    }

    public List getList() {
        return list;
    }

    public void setList(List list) {
        this.list = list;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getPageIndex() {
        return pageIndex;
    }

    public void setPageIndex(int pageIndex) {
        this.pageIndex = pageIndex;
    }

    public int getTotalRecords() {
        return totalRecords;
    }

    public void setTotalRecords(int totalRecords) {
        this.totalRecords = totalRecords;
    }
}

控制器类的代码如下:

注意,其中pageIndex和pageSize都需要从前端动态获取,并且页索引该从1开始,这里从0开始也只是为了演示使用,并且向前端应该返回page的所有对象。

service层的代码如下,没有什么注意的地方:

mapper层的代码如下:

注意:写SQL语句时必须使用指定limit

使用自定义拦截器分页

首先创建一个自定义拦截器MyPageIntercptor.java

/**
 * @Intercepts 说明是一个拦截器
 * @Signature 拦截器的签名
 * type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)
 * method 拦截的方法
 * args 参数,高版本需要加个Integer.class参数,不然会报错
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class})})
public class MyPageInterceptor implements Interceptor {

    //每页显示的条目数
    private int pageSize;
    //当前现实的页数
    private int pageIndex;
    //数据库类型
    private String dbType;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取StatementHandler,默认是RoutingStatementHandler
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //获取statementHandler包装类
        MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);

        //分离代理对象链
        while (MetaObjectHandler.hasGetter("h")) {
            Object obj = MetaObjectHandler.getValue("h");
            MetaObjectHandler = SystemMetaObject.forObject(obj);
        }

        while (MetaObjectHandler.hasGetter("target")) {
            Object obj = MetaObjectHandler.getValue("target");
            MetaObjectHandler = SystemMetaObject.forObject(obj);
        }

        //获取连接对象
        //Connection connection = (Connection) invocation.getArgs()[0];

        //object.getValue("delegate");  获取StatementHandler的实现类

        //获取查询接口映射的相关信息
        MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");
        String mapId = mappedStatement.getId();

        //statementHandler.getBoundSql().getParameterObject();

        //拦截以.ByPage结尾的请求,分页功能的统一实现
        if (mapId.matches(".+ByPage$")) {
            //获取进行数据库操作时管理参数的handler
            ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");
            //获取请求时的参数
            Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();
            //也可以这样获取
            //paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();

            //参数名称和在service中设置到map中的名称一致
            pageIndex = (Integer) paraObject.get("pageIndex");
            pageSize = (Integer) paraObject.get("pageSize");

            String sql = (String) MetaObjectHandler.getValue("delegate.boundSql.sql");
            //也可以通过statementHandler直接获取
            //sql = statementHandler.getBoundSql().getSql();

            //构建分页功能的sql语句
            String limitSql;
            sql = sql.trim();
            limitSql = sql + " limit " + (pageIndex - 1) * pageSize + "," + pageSize;

            //将构建完成的分页sql语句赋值个体'delegate.boundSql.sql',偷天换日
            MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);
        }
        //调用原对象的方法,进入责任链的下一级
        return invocation.proceed();
    }


    //获取代理对象
    @Override
    public Object plugin(Object o) {
        //生成object对象的动态代理对象
        return Plugin.wrap(o, this);
    }

    //设置代理对象的参数
    @Override
    public void setProperties(Properties properties) {
        //如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。
        String limit1 = properties.getProperty("limit", "10");
        this.pageSize = Integer.valueOf(limit1);
        this.dbType = properties.getProperty("dbType", "mysql");
    }
}

然后在mybatis的配置文件中将拦截器类配置进去:

    <!--配置自定义的拦截器插件-->
    <plugins>
        <plugin interceptor="com.demo.interceptor.MyPageInterceptor">
            <property name="limit" value="10"/>
            <property name="dbType" value="mysql"/>
        </plugin>
    </plugins>

控制器controller层代码:

service层代码如下:

注意:必须在service层使用Map集合来放参数,如果有条件也需要通过map集合来存放,并且mapper层的方法名必须是xxxByPage()。

mapper层代码如下:

注意:方法名的命名必须是xxxByPage(),参数是Map类型,而SQL语句也是查询所有。

使用PageHelper插件分页

首先需要导入相关依赖(这里使用maven工程):

        <!--mybatis-pagehelper分页-->
        <dependency>
           <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.0.0</version>
        </dependency>

接着在mybatis的配置文件中添加如下配置:

    <!--配置mybatis-pagehelper插件-->
    <plugins>
        <!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 使用MySQL方言的分页 -->
            <property name="helperDialect" value="mysql"/><!--如果使用mysql,这里value为mysql-->
            <property name="pageSizeZero" value="true"/>
        </plugin>
    </plugins>

然后就可以使用了,控制器类代码示例如下:

注意:需要在调用service层方法时传入pageIndex和pageSize,也需要从前端动态获取,这里仅作演示。

service层中的代码如下:

注意:只需要在调用mapper层方法之前添加如下代码

PageHelper.startPage(pageIndex,pageSize);

其中PageHelper.startPage()是固定格式的静态方法,传入的两个参数分别是页码和页记录条数。

最后是mapper层代码,SQL语句也不需要进行limit操作,也不需要传入页参数:

其实PageHelper方法也是使用Interceptor拦截器方式的一种三方实现,它内部帮助我们实现了Interceptor的功能。

所以我们不用自定义MyPageInterceptor这个类了。实际上也是在运行查询方法的时候,进行拦截,然后设置分页参数。所以PageHelper.startPage(pageIndex, pageSize)这一句需要显式调用,然后再执行userDao.findAll(),在查询所有用户信息的时候,会进行一个分页参数设置,让放回的结果只是分页的结果,而不是全部集合。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值