【Mybatis】Mybatis之分页查询准备手写lim

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

上文Mybatis之动态SQL介绍了Mybatis中很常用的动态标签,本来继续来介绍一下在Mybatis中十分常用的分页查询。废话不多说,开始今天的内容。

准备

  • 查询参数的POJO中,在offset参数的get方法中,对offset的值进行了相应的计算。
  • 查询结果的POJO中,在构造方法中对总页数pages进行了相应的计算。
// 封装查询参数的POJO
public class QueryCondition {
    /**
     * ID
     */
    private Integer id;
    /**
     * 名称
     */
    private String name;
    /**
     * 价格
     */
    private Integer price;
    /**
     * 分类
     */
    private Integer category;
    private List<Integer> categoryList;

    /**
     * 分页参数
     */
    //偏移量
    private Integer offset;
    //每页条数
    private Integer pageSize;
    //页数
    private Integer pageNum;
    // 计算分页的起始位置
    public Integer getOffset() {
        return ((pageNum == null || pageNum < 1 ? 1 : pageNum) - 1) * (pageSize == null ? 3 : pageSize);
    }

    // 省略其余get/set方法
}

// 封装查询结果的POJO
public class PageVO<T> {

    /**
     * 每页条数
     */
    private int pageSize;
    /**
     * 页码
     */
    private int pageNum;

    /**
     * 总页数
     */
    private int pages;
    /**
     * 总条数
     */
    private int total;

    /**
     * 当前页的数据
     */
    private List<T> data;

    public PageVO(int pageSize, int pageNum, int total, List<T> data) {
        this.pageSize = pageSize;
        this.pageNum = pageNum;
        this.total = total;
        this.data = data;
        this.pages = total / pageSize + (total % pageSize == 0 ? 0 : 1);
    }
    
    // 省略get/set方法
}

复制代码
  • 数据库数据

image.png

手写limit分页

  • 顾名思义,这种方法就是我们自己在SQL中添加limit关键字来实现分页;
<select id="findPageByHand" parameterType="org.apache.ibatis.z_run.pojo.QueryCondition" resultType="org.apache.ibatis.z_run.pojo.Purchase">
    select
    <include refid="Base_Column_List" />
    from purchase
    <where>
        <if test="id != null">
            And id = #{id,jdbcType=INTEGER}
        </if>
        <if test="category != null">
            And category = #{category,jdbcType=INTEGER}
        </if>
    </where>
    <if test="pageSize != null and pageNum != null">
        limit #{offset}, #{pageSize}
    </if>
</select>
复制代码
  • 另外,为了返回一个总条数,我们还得单独再写一条SQL,用于统计分页查询的总条数,在计算总页数的时候使用。
<select id="count" parameterType="org.apache.ibatis.z_run.pojo.QueryCondition" resultType="java.lang.Integer">
    select
    count(1)
    from
    (
        select
        <include refid="Base_Column_List"/>
        from purchase
        <where>
            <if test="id != null">
                And id = #{id,jdbcType=INTEGER}
            </if>
            <if test="category != null">
                And category = #{category,jdbcType=INTEGER}
            </if>
        </where>
    ) t1
</select>
复制代码
  • 测试代码及返回结果
int pageSize = 2;
int pageNum = 1;

@Test
public void pageQueryByHand() {
    PurchaseMapper mapper = sqlSession.getMapper(PurchaseMapper.class);
    QueryCondition condition = new QueryCondition();
    condition.setPageNum(pageNum);
    condition.setPageSize(pageSize);
    System.out.println(new PageVO<>(pageSize, pageNum, mapper.count(condition), mapper.findPageByHand(condition)));
}
复制代码
DEBUG [main] - ==>  Preparing: select count(1) from ( select id, `name`, price, category from purchase ) t1 
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 1
DEBUG [main] - Cache Hit Ratio [org.apache.ibatis.z_run.mapper.PurchaseMapper]: 0.0
DEBUG [main] - ==>  Preparing: select id, `name`, price, category from purchase limit ?, ? 
DEBUG [main] - ==> Parameters: 0(Integer), 2(Integer)
DEBUG [main] - <==      Total: 2
PageVO{pageSize=2, pageNum=1, pages=4, total=7, data=[Purchase{id=1, name='可乐', price=6, category=1}, Purchase{id=2, name='爆米花', price=18, category=2}]}
复制代码

RowBounds分页

  • RowBounds对象是Mybatis提供的一个分页类,只需要在查询的方法参数中加上这个对象即可使用。
<select id="findPageByRowBounds" parameterType="org.apache.ibatis.z_run.pojo.QueryCondition" resultType="org.apache.ibatis.z_run.pojo.Purchase">
    select
    <include refid="Base_Column_List" />
    from purchase
    <where>
        <if test="id != null">
            And id = #{id,jdbcType=INTEGER}
        </if>
        <if test="category != null">
            And category = #{category,jdbcType=INTEGER}
        </if>
    </where>
</select>
复制代码
  • 使用RowBounds对象后,就不需要在SQL语句中写limit语句了,但是仍然要对满足条件的数据条数进行单独查询,其结果将用于计算总页数,语句同上。
  • 测试代码及返回结果
    int pageSize = 2;
    int pageNum = 2;

    @Test
    public void pageQueryByRowBounds() {
        PurchaseMapper mapper = sqlSession.getMapper(PurchaseMapper.class);
        QueryCondition condition = new QueryCondition();
        System.out.println(new PageVO<>(pageSize, pageNum, mapper.count(condition),
                mapper.findPageByRowBounds(condition, new RowBounds((pageNum - 1) * pageSize, pageSize))));
    }
复制代码
DEBUG [main] - ==>  Preparing: select count(1) from ( select id, `name`, price, category from purchase ) t1 
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 1
DEBUG [main] - Cache Hit Ratio [org.apache.ibatis.z_run.mapper.PurchaseMapper]: 0.0
DEBUG [main] - ==>  Preparing: select id, `name`, price, category from purchase 
DEBUG [main] - ==> Parameters: 
PageVO{pageSize=2, pageNum=2, pages=4, total=7, data=[Purchase{id=8, name='火腿', price=3, category=1}, Purchase{id=9, name='火腿', price=3, category=1}]}
复制代码
  • 可以看到,使用RowBounds进行分页时,SQL语句中并没有添加limit关键字进行分页,这是因为RowBounds分页是将所有的数据查询到内存中以后,再使用RowBounds参数进行分页的,对内存的压力很大,性能很低,因此这种方式不建议使用。

分页插件

  • 这里主要介绍目前使用最广泛的Pagehelper插件。Pagehelper插件的原理是使用拦截器拦截SQL语句的执行,并为SQL语句添加limit关键字进行分页查询,以及count语句来查询总数(就不需要我们自己手写count方法来计算数据总条数了)。

  • 导入依赖

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>
复制代码
  • 配置文件
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
复制代码
  • XML语句同RowBounds相同,不需要在SQL语句中写limit关键字。
<select id="findByCondition" parameterType="org.apache.ibatis.z_run.pojo.QueryCondition" resultType="org.apache.ibatis.z_run.pojo.Purchase">
    select
    <include refid="Base_Column_List" />
    from purchase
    <where>
        <if test="id != null">
            And id = #{id,jdbcType=INTEGER}
        </if>
        <if test="category != null">
            And category = #{category,jdbcType=INTEGER}
        </if>
    </where>
</select>
复制代码
  • 测试代码及查询结果。查询结果的总条数以及总页数都可以在Page对象中直接获取。
int pageSize = 2;
int pageNum = 2;

@Test
public void pageQueryByPageHelper() {
    PurchaseMapper mapper = sqlSession.getMapper(PurchaseMapper.class);
    QueryCondition condition = new QueryCondition();
    // 这一行代码之后需要立即接上需要分页的查询方法,否则可能导致分页失效
    Page page = PageHelper.startPage(pageNum, pageSize);
    List<Purchase> purchaseList = mapper.findByCondition(condition);
    System.out.println(new PageVO<>(pageSize, pageNum, (int) page.getTotal(), purchaseList));
}
复制代码
DEBUG [main] - ==>  Preparing: SELECT count(0) FROM purchase 
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 1
DEBUG [main] - Cache Hit Ratio [org.apache.ibatis.z_run.mapper.PurchaseMapper]: 0.0
DEBUG [main] - ==>  Preparing: select id, `name`, price, category from purchase LIMIT ?, ? 
DEBUG [main] - ==> Parameters: 2(Long), 2(Integer)
DEBUG [main] - <==      Total: 2
PageVO{pageSize=2, pageNum=2, pages=4, total=7, data=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=7, pages=4, reasonable=false, pageSizeZero=false}[Purchase{id=8, name='火腿', price=3, category=1}, Purchase{id=9, name='火腿', price=3, category=1}]}
复制代码

Tips

  • Mybatis配置文件的标签顺序是有讲究的,如果顺序出问题,是会报错的。例如:
<!--指定Mapper.xml所在位置-->
<mappers>
    <mapper resource="resources/xml/PurchaseMapper.xml"/>
</mappers>
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
复制代码
  • 此时,配置文件报错,内容如下:

image.png

  • 运行项目报错,内容大体如下:
org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance.  
.
.
.
Caused by: org.xml.sax.SAXParseException; lineNumber: 101; columnNumber: 17; 元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)"。
复制代码

以上便是对于Mybatis中分页方式的介绍,一般来说,使用Pagehelper更为方便,但是引入第三方插件之后可能会有一些bug,需要在遇到之后进行仔细的排查。而自己手写分页不容易出现问题,但是比较麻烦。具体情况具体分析,根据自身情况挑选适合自己的方式:)