在使用Mybatis时,最头痛的就是写分页了,需要先写一个查询count的select语句,然后再写一个真正分页查询的语句,当查询条件多了之后,会发现真的不想花双倍的时间写 count 和 select,幸好我们有 pagehelper 分页插件,pagehelper 是一个强大实用的 MyBatis 分页插件,可以帮助我们快速的实现MyBatis分页功能,而且pagehelper有个优点是,分页和Mapper.xml完全解耦,并以插件的形式实现,对Mybatis执行的流程进行了强化,这有效的避免了我们需要直接写分页SQL语句来实现分页功能。
第1步:先把MyBatis环境配好,参考文章:Spring Boot 2.3.x 集成 MyBatis
第2步:在pom.xml
文件中,添加分页插件依赖包
<!-- MyBatis分页插件 --> <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency>
第3步:在application.yml
文件中,配置分页插件
# MyBatis分页插件 pagehelper: helperDialect: mysql reasonable: true supportMethodsArguments: true params: count=countSql
第4步:编写分页代码
(1) 在DAO接口层添加一个分页查找方法,这个查询方法跟查询全部数据的方法除了名称几乎一样。
Mapper接口文件:
@Mapper public interface BlogMapper { /** * 查询所有博文 */ List<Blog> selectAll(); /** * 分页查询博文 */ List<Blog> selectPage(); }
Mapper映射文件:
<select id="selectPage" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from blog </select>
(2) 控制器/服务层 中显示分页信息
package com.wenjianbao.controller; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.wenjianbao.entity.Blog; import com.wenjianbao.mapper.BlogMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import java.util.List; @Controller public class DefaultController { @Autowired private BlogMapper blogMapper; @GetMapping("/blog/list") @ResponseBody public String findUserPage(@RequestParam("pageNum") int pageNum, @RequestParam("pageSize") int pageSize) { // 设置分页信息 PageHelper.startPage(pageNum, pageSize); // 分页查询博文 List<Blog> blogList = blogMapper.selectPage(); // 获取分页信息 PageInfo<Blog> pageInfo = new PageInfo<>(blogList); // 输出 总记录数 等 分页信息 System.out.println(pageInfo.getTotal()); System.out.println(pageInfo.getPages()); System.out.println(pageInfo.getPageNum()); System.out.println(pageInfo.getPageSize()); return "ok"; } }
注意事项:
分页不安全的情况
PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。
只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。
如下代码是不安全的:
PageHelper.startPage(1, 10); List<Country> list; if (param1 != null) { list = countryMapper.selectIf(param1); } else { list = new ArrayList<Country>(); }
这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
写成如下便安全了:
List<Country> list; if (param1 != null) { PageHelper.startPage(1, 10); list = countryMapper.selectIf(param1); } else { list = new ArrayList<Country>(); }
另外也可以手动清理ThreadLocal存储的分页参数:
PageHelper.clearPage();
参考:
对 上述分页插件再二次封装一下
(1) 分页结果类:PageResult.java
package com.wanma.framework_web.model; import com.github.pagehelper.PageInfo; import java.util.List; /** * 分页查询结果封装类 */ public class PageResult<T> { private PageInfo<T> pageInfo; private Pagination pagination; /** * 快捷生成分页结果类 */ public static <T> PageResult<T> of(PageInfo<T> page) { PageResult<T> pageResult = new PageResult<>(); pageResult.pageInfo = page; pageResult.pagination = new Pagination(); pageResult.pagination.setPageNum((int) page.getCurrent()); pageResult.pagination.setPageSize((int) page.getSize()); pageResult.pagination.setTotalPages((int) page.getPages()); pageResult.pagination.setTotalSize(page.getTotal()); return pageResult; } /** * 当前页分页数 */ public long getPageNum() { return this.pageInfo.getCurrent(); } /** * 每页记录数 */ public long getPageSize() { return this.pageInfo.getSize(); } /** * 总记录数 */ public long getTotalSize() { return this.pageInfo.getTotal(); } /** * 总共分页数 */ public long getTotalPages() { return this.pageInfo.getPages(); } /** * 分页条 */ public String getPageStr() { return this.pagination.getPageStr(); } /** * 分页数据列表 */ public List<T> getList() { return this.pageInfo.getRecords(); } }
(2) 分页类:Pagination.java
package com.wanma.framework_web.model; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import com.wanma.framework_web.helper.RequestHelper; import java.util.Map; /** * 分页条类(输出分页字符串:首页、上一页、下一页、尾页) */ public class Pagination { private int pageNum = 1; // 【必填】当前分页数 private int pageSize = 10; // 【必填】每页记录数 private int totalPages = 0; // 【必填】总共分页数 private long totalSize = 0; // 【必填】总记录数 private int[] pageSizeOption = {10, 20, 30, 40, 50}; // 每页记录数 下拉选择项 private int maxPages = 0; // 最大能显示的分页数(比如说,本来总共有250页,但是想让它只显示前50页) private int columnPages = 5; // 分页栏显示的页数 private String basePageUrl = ""; /** * 构造方法 */ public Pagination() { init(); } /** * 初始化 */ private void init() { // 基础的URL构造器 UrlBuilder urlBuilder = UrlBuilder.of(RequestHelper.getPageUrl()); Map<CharSequence, CharSequence> urlParams = urlBuilder.getQuery().getQueryMap(); UrlQuery urlQuery = new UrlQuery(); String[] deleteParams = {"pageNum", "pageSize"}; for (Map.Entry<CharSequence, CharSequence> entry : urlParams.entrySet()) { if (!ArrayUtil.contains(deleteParams, entry.getKey())) { urlQuery.add(entry.getKey(), entry.getValue()); } } basePageUrl = urlBuilder.setQuery(urlQuery).toString(); // 总页数 if (maxPages > 0 && maxPages < totalPages) { totalPages = maxPages; } // 校正当前页,不能越界 pageNum = NumberUtil.max(pageNum, 1); pageNum = NumberUtil.min(pageNum, totalPages); } private String makeUrl(int pageNum) { UrlBuilder urlBuilder = UrlBuilder.ofHttp(basePageUrl); urlBuilder.addQuery("pageNum", String.valueOf(pageNum)); return urlBuilder.toString(); } private String makeUrl(int pageNum, int pageSize) { UrlBuilder urlBuilder = UrlBuilder.ofHttp(basePageUrl); urlBuilder.addQuery("pageNum", String.valueOf(pageNum)); urlBuilder.addQuery("pageSize", String.valueOf(pageSize)); return urlBuilder.toString(); } /** * 获取分页字符串 */ public String getPageStr() { StringBuilder pageSb = new StringBuilder(); int offset = NumberUtil.ceilDiv(columnPages, 2) - 1; int from = 0; int to = 0; if (totalPages < columnPages) { from = 1; to = totalPages; } else { from = pageNum - offset; to = from + columnPages - 1; if (from < 1) { from = 1; to = columnPages; } else if (to > totalPages) { from = totalPages - columnPages + 1; to = totalPages; } } // 首页 if (from > 1) { pageSb.append("<li><a href=\"") .append(makeUrl(1, pageSize)) .append("\" class=\"first\">首页</a></li>"); } // 上一页 if (pageNum > 1) { pageSb.append("<li><a href=\"") .append(makeUrl(pageNum - 1, pageSize)) .append("\" class=\"prev\">上一页</a></li>"); } // 中间数字页 for (int i = from; i <= to; i++) { if (i == pageNum) { pageSb.append("<li class=\"active\"><a>") .append(i) .append("</a></li>"); } else { pageSb.append("<li><a href=\"") .append(makeUrl(i, pageSize)) .append("\">") .append(i) .append("</a></li>"); } } // 下一页 if (pageNum < totalPages) { pageSb.append("<li><a href=\"") .append(makeUrl(pageNum + 1, pageSize)) .append("\" class=\"next\">下一页</a></li>"); } // 末页 if (to < totalPages) { pageSb.append("<li><a href=\"") .append(makeUrl(totalPages, pageSize)) .append("\" class=\"last\">末页</a></li>"); } // 每页记录数 下拉框 StringBuilder pageSizeSb = new StringBuilder(); pageSizeSb.append("<li>总共<strong>") .append(totalSize) .append("</strong>条记录,每页显示<select #onchange#>#option#</select>条记录 </li>"); // 下拉框 onChange事件 StringBuilder onchangeSb = new StringBuilder(); onchangeSb.append(" onchange=\"location.href='") .append(makeUrl(1)) .append("&pageSize=' + this.value\" "); StringBuilder optionSb = new StringBuilder(); for (int size : pageSizeOption) { String selectedStr = ""; if (pageSize == size) { selectedStr = " selected=\"selected\" "; } optionSb.append("<option value=\"") .append(size) .append("\" ") .append(selectedStr) .append(">") .append(size) .append("</option>") ; } String pageSizeStr = pageSizeSb.toString() .replace("#onchange#", onchangeSb.toString()) .replace("#option#", optionSb.toString()); return "<ul class=\"pagination clearfix\">" + pageSizeStr + pageSb.toString() + "</ul>"; } public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getTotalPages() { return totalPages; } public void setTotalPages(int totalPages) { this.totalPages = totalPages; } public long getTotalSize() { return totalSize; } public void setTotalSize(long totalSize) { this.totalSize = totalSize; } public int[] getPageSizeOption() { return pageSizeOption; } public void setPageSizeOption(int[] pageSizeOption) { this.pageSizeOption = pageSizeOption; } public int getMaxPages() { return maxPages; } public void setMaxPages(int maxPages) { this.maxPages = maxPages; } public int getColumnPages() { return columnPages; } public void setColumnPages(int columnPages) { this.columnPages = columnPages; } }
(3) 使用方式:
@GetMapping("/blog/list") @ResponseBody public String findUserPage(@RequestParam("pageNum") int pageNum, @RequestParam("pageSize") int pageSize) { // 设置分页信息 PageHelper.startPage(pageNum, pageSize); // 分页查询博文 List<Blog> blogList = blogMapper.selectPage(); // 获取分页信息 PageResult<Blog> pageResult = PageResult.of(new PageInfo<>(blogList)); // 分页条Html代码 System.out.println(pageResult.getPageStr()); return "ok"; }