分页查询实现

关于分不分页的问题

套路:如果查询数据不确定多少条,都分了再说,花时间考虑哪些数据分页,哪些数据部分也,最后考虑的也不一定准确,干脆统一标准都分了再说,如果有些数据确实不需要分页显示结果,可以给它每页给一个很大的值(Integer.MAX_VALUE),这样就可以达到不分页的效果。有些数据特征是不需要分页的,收货地址每个省 每个市,这些数据比较稳定,改变的频率不是很大。

分页的选用 

SQL语句中的LIMIT进行分页是有弊端的,因为在做项目时,这就分页数据是要返回给前端来使用的,我们也有见过一些网站的数据展示分页下面还会显示翻页的按钮,可以跳到指定页,这些数据前端都需要知道,最大页码、最小页码、当前页码、总页码等等,都需要一这些数据来作为参照实现翻页功能,而LIMIT只能返回按指定的每页多少条、分多少页,数据不够完善。 分页查询不光要有LIMIT查询的结果,还要做统计查询,统计数据量,才能算出每页有几条。

添加分页框架依赖

    <!-- PageHelper:专用于MyBatis的分页框架 -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.3.0</version>
    </dependency>

PageHelper分页库

简介

  1. PageHelper是一个用于Java的分页库。它提供了一组实用方法,用于帮助处理大型结果集的分页。
  2. 使用PageHelper,你可以轻松地将分页添加到你的SQL查询中,只需修改SQL语句并设置适当的页码和页大小。PageHelper将处理剩下的工作,包括生成正确的SQL语法和提取所需的结果集。
  3. PageHelper支持各种数据库,包括MySQL、Oracle、PostgreSQL和SQL Server。它与流行的Java框架,如Spring和MyBatis集成。

基本使用

使用PageHelper分页库,在数据库查询语句上自动做了两件事,首先自动做了统计查询,其次在自己xml写的查询语句后面补了 “LIMIT ?, ?”.;详细功能进Page源码查看。

    @Test
    void listTagType() {
        Integer pageNum = 2; // 页码,从1开始顺序编号
        Integer pageSize = 2; // 每页多少条数据
        PageHelper.startPage(pageNum, pageSize); // 设计分页参数
        List<?> list = mapper.listTagType(); // 【注意】必须紧随“PageHelper.startPage()”之后,否则,可能产生线程安全问题
        System.out.println("查询列表完成,列表类型:" + list.getClass().getName());
        System.out.println(list);
        System.out.println("列表项的数量:" + list.size());
        for (Object item : list) {
            System.out.println("列表项:" + item);
        }
    }

运行结果:

 打印分页后list结果

Page{
    count=true,
    pageNum=2,
    pageSize=2,
    startRow=2,
    emdRow=4,
    total=7,
    pages=4,
    reasonable=false,
    pageSizeZero=false,
}
[
    TagTypeListItemVO(id=5,name="xxx",enable=1,sort=99),
    TagTypeListItemVO(id=4,name="xxxx",enable=0,sort=66)
]

 使用PageInfo处理分页结果

    PageInfo<?> pageInfo = new PageInfo<>(list);
    System.out.println(pageInfo);

打印pageInfo结果:(例子:查的是第二页,每页显示两条,总共数据库7条数据)

Page{
    pageNum=2,
    pageSize=2,
    size=2
    startRow=3,
    emdRow=4,
    total=7,
    pages=4,
    list=Page{
        count=true,pageNum=2,pageSize=2,startRow=3,endRow=4,total=7,pages=4,
        reasonable=false,pageSizeZero=false
    }
    [
        TagTypeListItemVO(id=5,name="xxx",enable=1,sort=99),
        TagTypeListItemVO(id=4,name="xxxx",enable=0,sort=66)
    ],
    prePage=1,                      //当前页的上一页
    nextPage=3,                     //当前页的下一页
    isFirstPage=false,              //当前页是否是第一页
    isLastPage=false,               //当前页是否是最后一页
    hasPreviousPage=true,           //当前页是否有上一页
    hasNextPage=true,               //当前页是否有下一页
    navigatePages=8,                //默认导航页数
    navigateFirstPage=1,            //默认导航第一页
    navigateLastPage=4,             //默认导航最后一页
    navigatepageNums=[1,2,3,4],     //实际分多少页
}

Mapper层分页功能接口设计

    /**
     * 查询标签类别列表
     *
     * @return 标签类别列表
     */
    List<TagTypeListItemVO> listTagType();

 XML文件配置SQL语句

    <!-- List<TagTypeListItemVO> listTagType() -->
    <select id="listTagType" resultMap="TagTypeListItemResultMap">
        SELECT
            <include refid="ListQueryFields"/>
        FROM
            content_tag
        WHERE
            parent_id=0
        ORDER BY
            sort DESC, id DESC
    </select>

    <sql id="ListQueryFields">
        <if test="true">
            id, 
            name,
            enable,
            sort
        </if>
    </sql>

    <resultMap id="TagTypeListItemResultMap"
               type="com.xfk.tea.admin.server.content.pojo.vo.TagTypeListItemVO">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="enable" property="enable"/>
        <result column="sort" property="sort"/>
    </resultMap>

Repositoty层分页功能接口设计

    /**
     * 查询标签类别列表
     * @param pageNum  页码
     * @param pageSize 每条记录数
     * @return 标签类别列表的分页数据
     */
    PageData<TagTypeListItemVO> listTagType(Integer pageNum, Integer pageSize);

为什么设计在repositoty层

service层不应该实现分页查询功能,service层应该给个页码就可以查询出来结果,service层应该负责业务逻辑,就实现在repositoty。

返回值的选用

选用PageInfo作为返回值,而不选用Page作为返回值,原因是遵循设计大于需求原则,对于这两个API用哪个编码成本都不是很高,优先选择功能多的。其实很多程序的功能很多,对于用户也不一定用的上,甚至有些功能都不认识。

解耦的角度考虑,使用PageInfo也不是很好。代码正常运行的时候,repositoty层的调用者是service层,作为调用者就必须要是别PageInfo,调用者为了调用分页功能的方法,必须要有PageInfo的导包语句,问题就来了,如果未来程序要变动,将不使用PageHelper分页框架,那么service层的调用分页功能的导包语句也就会用不了,这样的话,service层也就依赖了PageHelper框架,不使用PageHelper框架就会导致service层也要改动。service层除了基础依赖项不依赖于其他框架,那些框架解决的是技术性上的问题,而service主要解决规则逻辑。

解决方案

可以自己写一个类似于PageInfo的类,用自己写的类型作为返回值类型。

/**
 * 分页数据类
 */
@Data
@Accessors(chain = true)
public class PageData<T> implements Serializable {
    /**
     * 每页记录数
     */
    private Integer pageSize;
    /**
     * 记录总数
     */
    private Long total;
    /**
     * 当前页码
     */
    private Integer currentPage;
    /**
     * 最大页码
     */
    private Integer maxPage;
    /**
     * 数据列表
     */
    private List<T> list;
}

Repositoty层分页功能实现类设计

    @Override
    public PageData<TagTypeListItemVO> listTagType(Integer pageNum, Integer pageSize) {
        log.debug("开始执行【查询标签类别列表】,页码:{},每页记录数:{}", pageNum, pageSize);
        PageHelper.startPage(pageNum, pageSize);
        List<TagTypeListItemVO> tagTypeList = tagMapper.listTagType();
        PageInfo<TagTypeListItemVO> pageInfo = new PageInfo<>(tagTypeList);
        PageData<TagTypeListItemVO> pageData = new PageData<>();
        pageData.setPageSize(pageInfo.getPageSize());
        pageData.setTotal(pageInfo.getTotal());
        pageData.setCurrentPage(pageInfo.getPageNum());
        pageData.setMaxPage(pageInfo.getPages());
        pageData.setList(pageInfo.getList());
        return pageData;
    }

上述代码将PageInfo转化为PageData时,设置了一堆值,看起来不简洁。可以在PageData类上加一个注解@Accessors(chain = true),改为链式调用法。

    PageData<TagTypeListItemVO> pageData = new PageData<>();
    pageData.setPageSize(pageInfo.getPageSize())
            .setTotal(pageInfo.getTotal())
            .setCurrentPage(pageInfo.getPageNum())
            .setMaxPage(pageInfo.getPages())
            .setList(pageInfo.getList());

@Accessors(chain = true)注解实现原理:加上此注解,它改变了对象属性的set方法的返回值,返回当前的对象。对象打点调用理所当然。

    //加注解之前的set方法
    public void setTotal(Long total){
        this.total=total;
    }
    //加注解之后的set方法
    public PageData setTotal(Long total){
        this.total=total;
        return this;
    }

其实这个将PageInfo转化为PageData的过程是一成不变的,每次分页查询都要写一堆这样的代码,效果不是很好。将此功能封装成一个类放在自定义工具包,每次使用就每次调用就行。

注意:此类中的方法加了锁synchronized,方法在很多分页查询的地方都需要使用,避免线程安全问题。

    /**
     * 将PageInfo转换成PageData的转换器工具类
     */
    public class PageInfoToPageDataConverter {
        /**
         * 将PageHelper框架中的PageInfo类型对象转换成自定义的PageData类型对象
         *
         * @param pageInfo PageInfo对象
         * @param <T>      PageInfo对象中的列表数据中的元素数据的类型
         * @return 自定义的PageData类型的对象
         */
        public synchronized static <T> PageData<T> convert(PageInfo<T> pageInfo) {
            PageData<T> pageData = new PageData<>();
            pageData.setPageSize(pageInfo.getPageSize())
                    .setTotal(pageInfo.getTotal())
                    .setCurrentPage(pageInfo.getPageNum())
                    .setMaxPage(pageInfo.getPages())
                    .setList(pageInfo.getList());
            return pageData;
        }
    }

改善后的Repositoty层分页功能实现类设计

    @Override
    public PageData<TagTypeListItemVO> listTagType(Integer pageNum, Integer pageSize) {
        log.debug("开始执行【查询标签类别列表】,页码:{},每页记录数:{}", pageNum, pageSize);
        PageHelper.startPage(pageNum, pageSize);
        List<TagTypeListItemVO> tagTypeList = tagMapper.listTagType();
        PageInfo<TagTypeListItemVO> pageInfo = new PageInfo<>(tagTypeList);
        PageData<TagTypeListItemVO> pageData = PageInfoToPageDataConverter.convert(pageInfo);
        return pageData;
    }

Service层分页功能接口设计

这里接口设计两个原因:对于外部的调用者或使用者不太需要关心每页多少条数据,平时上网浏览内容的时候,大部分列表可能都没给提供每页多少条的设置功能,默认就做好了每页的条数,service中每一个接口都是一个业务,对于普通用户来说一个业务就是一个功能,大多情况下,普通用户是不会关心每页显示多少条数据,普通用户的基本目的是能看到数据就行。页码这种参数,让用户提交是不靠谱的,用户并不知道多少页数据,总共就是10页数据,用户查30页数据没有意义。保留两个版本用起来比较灵活。

    /**
     * 查询标签类别列表,将使用默认的“每页记录数”
     *
     * @param pageNum 页码
     * @return 标签类别列表的分页数据
     */
    PageData<TagTypeListItemVO> listTagType(Integer pageNum);
    /**
     * 查询标签类别列表
     *
     * @param pageNum  页码
     * @param pageSize 每条记录数
     * @return 标签类别列表的分页数据
     */
    PageData<TagTypeListItemVO> listTagType(Integer pageNum, Integer pageSize);

Service层分页功能实现类设计

service层一般查询没有什么业务规则,就算查不到也不是错误,刚注册微信,好友列表是空的.权限问题不是工作范畴。

自己设置好默认页码,直接给数字传递参数是不够通俗的,以后改起来也比较麻烦。如果在全局定义一个常量也是不够完美,其实可以更加灵活一点,写到配置文件里面,在项目完成后,甲方不喜欢自定义的默认显示页数,项目部署上线时可以自己去修改,用以查找修改。改善做法:

#自定义配置(名字自定义,一般和项目名相关 易读)
tea-store:
  # 数据访问的相关配置
  dao:
    # 查询数据时,默认的每页记录数,建议值为10~30之间
    default-query-page-size: 5
    @Value("${tea-store.dao.default-query-page-size}")
    private Integer defaultQueryPageSize;

    @Override
    public PageData<TagTypeListItemVO> listTagType(Integer pageNum) {
        log.debug("开始处理【查询标签类别列表】业务,页码:{}", pageNum);
        PageData<TagTypeListItemVO> pageData = tagRepository.listTagType(pageNum, defaultQueryPageSize);
        return pageData;
    }

    @Override
    public PageData<TagTypeListItemVO> listTagType(Integer pageNum, Integer pageSize) {
        log.debug("开始处理【查询标签类别列表】业务,页码:{},每页记录数:{}", pageNum, pageSize);
        PageData<TagTypeListItemVO> pageData = tagRepository.listTagType(pageNum, pageSize);
        return pageData;
    }

Controller层设计实现

4开头查询,40做统计,41查单个,42查列表

遵循restful风格,查列表的话请求路径值可以不写的。但是查询的是类型列表,就写了路径值。

@ApiImplicitParam加上这个注解之后API开发文档中dataType自动变成String,应显示加 dataType = "int"

后续操作标签的时候,要展示所有标签类别,因此这里需要加一个展示所有结果的功能。

    @ApiOperation("查询标签类别列表")
    @ApiOperationSupport(order = 420)
    @ApiImplicitParams({
        @ApiImplicitParam(name = "page", value = "页码", dataType = "int"),
        @ApiImplicitParam(name = "queryType", value = "查询类型,当需要查询全部数据时,此参数值应该是all")
    })
    @GetMapping("/type/list")
    @PreAuthorize("hasAuthority('/content/tag/read')")
    public JsonResult listTagType(Integer page, String queryType) {
        log.debug("开始处理【查询标签类别列表】请求,页码:{}", page);
        if (page == null) {
            page = 1;
        }
        Integer pageNum = page > 0 ? page : 1;
        PageData<TagTypeListItemVO> pageData ;
        if ("all".equals(queryType)) {
            pageData = tagService.listTagType(1, Integer.MAX_VALUE);
        } else {
            pageData = tagService.listTagType(pageNum);
        }
        return JsonResult.ok(pageData);
    }

前端发请求实现

<script>
export default {
  data() {
    return {
      // 表格数据
      tableData: [],
      // 分页相关数据
      currentPage: this.$router.currentRoute.query.page ? parseInt(this.$router.currentRoute.query.page) : 1,
      pageSize: 0,
      total: 0
    }
  },
  methods: {
    // 切换分页
    changePage(page) {
      //this.$router获取前端访问本页面路径
      this.$router.replace('?page=' + page);
      this.loadTagTypeList();
    },
    // 加载标签类别列表
    loadTagTypeList() {
      //获取前端展示效果的当前url的?后面的参数的page
      let page = this.$router.currentRoute.query.page;
      if (!page) {
        page = 1;
      }
      let url = 'http://localhost:9080/content/tags/type/list?page=' + page;
      console.log('url = ' + url);
      this.axios.get(url).then((response) => {
        let jsonResult = response.data;
        if (jsonResult.state == 20000) {
          this.tableData = jsonResult.data.list;
          this.currentPage = jsonResult.data.currentPage;
          this.pageSize = jsonResult.data.pageSize;
          this.total = jsonResult.data.total;
        } else {
          let title = '操作失败';
          this.$alert(jsonResult.message, title, {
            confirmButtonText: '确定',
            callback: action => {
            }
          });
        }
      }).catch(error => {
        let title = '错误';
        let message = '程序执行过程中出现错误,请打开浏览器的控制台查看详细错误信息!';
        this.$alert(message, title, {
          confirmButtonText: '确定',
          callback: action => {
          }
        });
        console.log(error);
      });
    }
  },
  //页面打开就加载
  mounted() {
    this.loadTagTypeList();
  }
}
</script>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值