数据列表的分页实现————分页敏捷开发

概要

分页功能是比较常见的基础功能,虽然比较简单,但是每次需要用到这个功能的时候还是需要现写一遍。为了实现更加宏观的业务复用,特将本人特别喜欢的简易分页逻辑在此记述,以备日后重用。

逻辑描述

一般的分页实现方式多是通过SQL语句“LIMIT”子句进行分页的,如果不清楚LIMIT子句的同学,还请先行了解此子句。

实际上,分页功能最重要的两个参数就是pageSize(每页条数)和pageWant(请求页码),这两个参数都是int型。

pageSize自不必多说,我来说说pageWant,我们常用的“上一页”“下一页”、以及“跳到...页”(当然,对于上一页和下一页的情况,页面需要维护一个全局的当前页的变量,每次请求后都需要更新这个当前页的变量,那么再次发起请求的时候,上一页就是当前页减一,下一页就是当前页加一)都是通过这个参数来请求后端的。这基本解决了前端数据请求的绝大多数情况。另外前端可能需要的几个重要的数值,比如:总页数,总条数 都可以由后台根据相关参数计算生成。

首先,我们可以先定义一个返回值的封装类,这个类中包含了页面分页请求后需要得到的全部数据。

然后,在controller接口的参数列表中,设置int pageSize 和 int pageWant,这里注意,pageSize如果页面可选,则传入,如果就固定条数,甚至可以在后台直接写死即可。

紧接着,将两个分页参数和其他筛选条件一同传入DAO层,由SQL语句直接进行操作和计算。

最后将返回值封装为我们一开始定义的封装类中,直接返回到页面即可。

功能实现

定义返回Wrapper类型

我们的返回值类型中包含页面所需的全部信息,包括基本的总页数,总条数,请求的页码,每页条数,以及最重要的:(经过筛选条件过滤之后的)单页记录列表。如下所示:

import java.util.List;public class PageForDataList<T> {/** 每页条数*/private int pageSize;/** 数据总条数*/private int dataAmount;/** 总页数*/private int pageAmount;/** 请求页数*/private int wantPage;/** 单页记录列表list*/private List<T> dataList;public PageForDataList() {}public PageForDataList(int pageSize, int dataAmount, int pageAmount, int wantPage, List<T> dataList) {super();this.pageSize = pageSize;this.dataAmount = dataAmount;this.pageAmount = pageAmount;this.wantPage = wantPage;this.dataList = dataList;}public int getPageSize() {return pageSize;}public void setPageSize(int pageSize) {this.pageSize = pageSize;}public int getDataAmount() {return dataAmount;}public void setDataAmount(int dataAmount) {this.dataAmount = dataAmount;}public int getPageAmount() {return pageAmount;}public void setPageAmount(int pageAmount) {this.pageAmount = pageAmount;}public int getWantPage() {return wantPage;}public void setWantPage(int wantPage) {this.wantPage = wantPage;}public List<T> getDataList() {return dataList;}public void setDataList(List<T> dataList) {this.dataList = dataList;}
}

设置接口参数

根据页面中的筛选条件的不同,参数有多有少有不同的情况,但是基本都是如下这样的结构:

    @ApiOperation("获取特价推荐门票列表,返回值为json结构体")@GetMapping(value = "/special_price/ticket/list")public PageForDataList<SpecialTicketPrice> specialPriceList(@ApiParam(value = "每页条数") @RequestParam(defaultValue = "10", required = true) int pageSize,@ApiParam(value = "请求页数") @RequestParam(defaultValue = "1", required = true) int wantPage,@ApiParam(value = "景区名称") String scenicName, @ApiParam(value = "所在地") String scenicLocation,@ApiParam(value = "推荐状态") Integer rmdStatus, @ApiParam(value = "折扣起始") Double rateStart,@ApiParam(value = "折扣终止") Double rateEnd) {return sprSvc.ticketList(pageSize, wantPage, scenicName, scenicLocation, rmdStatus, rateStart, rateEnd);}

其中,参数列表前两项为分页请求的数据,其余全部是筛选条件。sprSvc是一个service。

DAO层的分页实现

由于我们是通过LIMIT子句来实现分页功能,因此不论如何,都是要将请求的页码传入SQL来操作的。实际上,Service层在一个简单的分页查询的功能中仅仅充当一个Controller层与DAO层数据交互的传递信息的角色。如下service仅供参考:

    @Overridepublic PageForDataList<SpecialTicketPrice> ticketList(int pageSize, int wantPage, String scenicName,String scenicLocation, Integer rmdStatus, Double rateStart, Double rateEnd) {// 直接调用DAO中的查询SQLPageForDataList<SpecialTicketPrice> pdl = mngDao.findSpecialTicketList(pageSize, wantPage, scenicName,scenicLocation, rmdStatus, rateStart, rateEnd);return pdl;}

紧接着DAO层的关键实现代码如下:

    /*** 分页查询特价推荐门票列表* <br>作者: mht<br> * 时间:2018年5月7日-上午11:07:51<br>* @return*/public PageForDataList<SpecialTicketPrice> findSpecialTicketList(int pageSize, int wantPage, String scenicName, String scenicLocation, Integer rmdStatus, Double rateStart,Double rateEnd) {StringBuilder sqlBuilder = new StringBuilder("SELECT a.* FROM special_ticket_price a, scenic_sequence b WHERE a.seco_scenic_id = b.seco_scenic_id ");if (scenicName != null && !scenicName.equals("")) {sqlBuilder.append("AND b.scenic_name LIKE '%" + scenicName + "%' ");}if (scenicLocation != null) {sqlBuilder.append("AND a.location = '" + scenicLocation + "' ");}if (rmdStatus != null) {sqlBuilder.append("AND a.rmd_status = " + rmdStatus + " ");}if (rateStart != null) {sqlBuilder.append("AND a.discount_rate >= " + rateStart + " ");}if (rateEnd != null) {sqlBuilder.append("AND a.discount_rate <= " + rateEnd + " ");}// 查询总条数sqlString countSql = sqlBuilder.toString().replaceAll("a.\\*", "COUNT(*)");sqlBuilder.append("ORDER BY a.seco_product_id LIMIT " + pageSize * (wantPage - 1) + "," + pageSize);// 查询列表List<SpecialTicketPrice> list = jdbc.query(sqlBuilder.toString(),new BeanPropertyRowMapper<>(SpecialTicketPrice.class));// 查询dataAmount,数据总条数int dataAmount = jdbc.queryForObject(countSql, int.class);return new PageForDataList<SpecialTicketPrice>(pageSize, dataAmount,(int) Math.ceil(1.0 * dataAmount / pageSize), wantPage, list);}

从如上代码中,我们看到,我建立了一个StringBuilder来处理单线程下的查询列表的SQL语句sqlBuilder,然后我利用联表查询,并将五个参数通过if条件拼接到sqlBuilder后。

这里注意,因为不论是什么系统,分页查询一定都是带着筛选条件之后的分页数据列表,这个很好理解,比如我们在某宝买衣服,我以“西服”+ "上衣"作为筛选条件,结果分页之后却出现了裤子、皮鞋、衬衫、内衣等等,这就完全不符合实际需求。换句话说,分页功能的实现一定是建立在筛选条件之下的一个功能

在代码中,查询总条数的SQL语句的位置很讲究:

// 查询总条数sql
String countSql = sqlBuilder.toString().replaceAll("a.\\*", "COUNT(*)");

可以看到,这句SQL是将SELECT子句中的“a.*”替换为了“COUNT(*)”用于查询符合条件的记录总条数。而其他条件不变。“\\”则是为了完成“*”的转义。

为什么说这句SQL的位置讲究?

因为它是在筛选条件拼接到sqlBuilder之后才进行总数SQL的变化,这恰恰说明了我刚才提到的,分页查询在筛选条件之后的思想。其次,也是非常重要的一点是:countSql的定义,一定要在LIMIT子句之前。换句话说,总数查询的SQL语句一定不能带LIMIT子句!稍微一思考就会明白,我们查询的COUNT(*)应该是符合条件的全部记录条数,也就是在上述代码偏后的位置定义的dataAmount变量,如果COUNT查询在LIMIT子句之后拼入SQL语句(也就是最终得到的是一个带着LIMIT子句的COUNT查询),那么我们查询的结果,也就是记录总条数dataAmount将始终会小于等于pageSize。不服的同学,可以亲自试一试。

( 还要为基础欠佳的同学补充一点的是,请求分页的LIMIT表达式应该符合如下公式:

LIMIT pageSize * (wantPage - 1) , pageSize

其中,pageSize是从前台传入的 1 ,2,3....这样的正整数。)

然后,我们通过jdbcTemplate来对列表查询和COUNT查询的两条SQL语句进行分别查询,并赋值给list和dataAmount,从而得到单页的数据列表和符合条件的总条数

最后,return的时候,我直接通过最开始定义的返回值封装类的构造器将我们得到的数据进行封装返回到controller层。

其中需要通过数学函数 Math.ceil() 求得的总页数是这样的:

(int) Math.ceil(1.0 * dataAmount / pageSize)

这句话的意思是,用总条数dataAmount除以每页条数pageSize,然后由于除不尽的原因,我们需要事先将dataAmount变换成浮点型数据,然后这样我们就可以得到一个double类型的数据,再通过 Math.ceil() 函数,将浮点型数向上取整,再强转int得到结果。比如,dataAmount = 19,pageSize = 10,那么如上表达式的结果应该是2,也就是总共两页。

最后,我们拿到了这样一个封装好的数据传给controller,再通过return,返回给页面即可。

最终效果展示

由于是测试数据,因此数据并不多,我们可以通过执行的SQL在数据库中做同样的查询看一下结果:

 

综上,就是对分页功能的简单实现。

如有疑问,欢迎文末留言。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558997.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Eclipse深度患者设置VSCode快捷键

VSCode设置Eclipse中常用的快捷键 将eclipse中一些基本的快捷键输入右侧用户快捷键设置中&#xff1a; // Place your key bindings in this file to overwrite the defaults [{ "key": "alt/", "command": "editor.action.triggerSugges…

NodeJS学习————关于let和const命令的使用理解

let的基本用法 在新的js规范ES6中&#xff0c;新增了let 命令&#xff0c;用来声明变量。用法类似于var&#xff0c;但不同的是所声明的变量&#xff0c;只在let 命令所在的代码块内有效。 { let a 10; var b 10; } //ReferenceError: a is not defined console.log(a …

forward和redirect的区别是什么?

forward和redirect是什么&#xff1f; 是servlet种的两种主要的跳转方式。forward又叫转发&#xff0c;redirect叫做重定向。 区别&#xff1a;&#xff08;本地效应次数&#xff09; 地址栏&#xff0c;数据共享&#xff0c;应用场景&#xff0c;效率&#xff0c;本质&…

MYSQL的索引类型:PRIMARY, INDEX,UNIQUE,FULLTEXT,SPAIAL 有什么区别?各适用于什么场合?

一、MySQL索引类型 MySql常见索引类型有&#xff1a;主键索引、唯一索引、普通索引、全文索引、组合索引 PRIMARY KEY&#xff08;主键索引&#xff09; ALTER TABLE table_name ADD PRIMARY KEY ( column ) UNIQUE(唯一索引) ALTER TABLE table_name ADD UNIQUE (colu…

Servlet入门总结

一、了解Servlet的概念Servlet定义&#xff1a;Servlet是基于Java技术的Web组件&#xff0c;由容器管理并产生动态的内容。Servlet引擎作为WEB服务器的扩展提供支持Servlet的功能。Servlet与客户端通过Servlet容器实现的请求/响应模型进行交互。 注意&#xff1a;Servlet不是从…

MySQL日期类型的处理总结

一、概述 MySQL中的日期类型包括以下5种&#xff1a; 类型大小 (字节)范围格式用途DATE31000-01-01/9999-12-31YYYY-MM-DD日期值TIME3-838:59:59/838:59:59HH:MM:SS时间值或持续时间YEAR11901/2155YYYY年份值DATETIME81000-01-01 00:00:00/9999-12-31 23:59:59YYYY-MM-DD HH:…

详解HTTP协议~~~

详解HTTP协议~~~HTTP 简介HTTP协议是Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;的缩写,是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。。HTTP是一个基于TCP/IP通信协议来传递数据&#xff08;…

Mybatis Plus————代码生成器

代码生成器 MyBatis Plus是MyBatis的扩展框架&#xff0c;而代码生成器是MP的核心功能之一&#xff0c;另外还有 “条件构造器”和“通用CRUD”等功能。 步骤演示 mp的代码生成器有两种方式自动生成代码&#xff0c;一种是通过main方法来执行程序&#xff0c;另一种是通过maven…

Spring MVC 流程图解析

Spring MVC 流程图解析Spring MVC工作流程图图一图二 SpringMVC工作流程描述DispatcherServlet&#xff0c;HandlerMapping&#xff0c;HandlerExecutionChain&#xff0c;HandlerAdapter&#xff0c;HttpMessageConveter&#xff0c;BindingResult&#xff0c;ModelAndView&am…

Java并发编程实战————可重入内置锁

引言 在《Java Concurrency in Practice》的加锁机制一节中作者提到&#xff1a; Java提供一种内置的锁机制来支持原子性&#xff1a;同步代码块。“重入”意味着获取锁的操作的粒度是“线程”&#xff0c;而不是调用。当某个线程请求一个由其他线程持有的锁时&#xff0c;发出…

java的守护进程与非守护进程

java的守护进程与非守护进程 最近重新研究Java基础知识&#xff0c;发现以前太多知识知识略略带过了&#xff0c;比较说Java的线程机制&#xff0c;在Java中有两类线程&#xff1a; User Thread(用户线程)、Daemon Thread(守护线程) &#xff0c;&#xff08;PS:以前忽略了&a…

双剑合璧————Spring Boot + Mybatis Plus

引言 最近在学习Mybatis Plus的使用&#xff0c;希望通过spring boot快速将mybatis plus整合进来。 对于springboot项目&#xff0c;mybatis plus团队也有自己的启动器 &#xff1a;mybatis-plus-boot-starter。这个依赖内部已经整合了mybatis-spring&#xff0c;也包括非快速…

Git初学札记(一)————Git简介与安装

前言 Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。Git是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。&#xff08;在这里再一次致敬Linus大神&#xff09;特点 分布式相比于集中式的最…

Git初学札记(二)————EGit导入远程Git仓库项目(Clone操作)

引言 我们在实际开发项目的时候&#xff0c;难免要使用像Eclipse或者IDEA这样的继承开发工具&#xff0c;除了部分“牙牙学语”的程序员需要手动输入javac去编译程序以外&#xff0c;在实际开发中手动编译并运行项目的“猿族”应该是已经绝种了。 我个人认为&#xff0c;使用gi…

Git初学札记(三)————创建Git版本库

引言 版本库即所谓的Git仓库&#xff0c;英文名称是Repository&#xff0c;可以简单理解为一个目录&#xff08;.git folder&#xff09;&#xff0c;这个目录可以记录并保存直接父级及其子目录下的全部文本文件的修改操作&#xff0c;谓之“版本控制”&#xff01; 手动建库 不…

Git初学札记(四)————Git Push的常规操作与Pull冲突解决

目录 引言 Git命令行的远程Push EGit Push操作中的冲突问题 同步 工作区与本地库同步 工作区与远程库同步 图标 重点 引言 在团队开发当中&#xff0c;Git Push是多人协作环节中的最重要的一环可能没有之一。同SVN一样&#xff0c;push操作可以看做是对远端程序的提交…

Git初学札记(五)————Branch分支管理

引言 正如之前的博客中提到的&#xff0c;Git区别于Svn的一个最明显的功能就是分支管理功能。 那么什么是分支&#xff1f;分支又能为我们的开发带来什么翻天覆地的变化呢&#xff1f;&#xff08;为了使博客的内容更具权威性和专业性&#xff0c;以下部分内容摘自官方文档《Gi…

Git初学札记(六)————在远程新建本地Branch与在本地新建远程Branch

引言 本篇博客介绍将现有的本地分支以新的分支形式推送到远程库中&#xff0c;和以新的分支的形式从远程库中拉取一个分支。这两个功能都是比较简单的操作&#xff0c;但是在实际开发中&#xff0c;可能会在开发初期有所触及。比如我们希望将远程的dev分支拉取到本地来进行开发…

Git初学札记(七)————合并分支(merge)

目录 引言 开始Merge 1、History视图 2、Team菜单 3、Git Repositories视图 巧用Git Staging视图 放弃Merging 可能的Merge结果 引言 Git鼓励开发者使用分支来进行程序的开发。但是最终只会有一个版本发行出去&#xff0c;因此&#xff0c;我们需要将开发好的分支merg…

公钥,私钥和数字签名这样理解轻松入门!

公钥&#xff0c;私钥和数字签名这样理解轻松入门&#xff01;参考博文&#xff1a;https://blog.csdn.net/21aspnet/article/details/7249401 &#xff08;公钥和私钥是成对出现的&#xff0c;可以把他们看成锁头和钥匙的关系&#xff0c;公钥为锁头&#xff0c;私钥是钥匙&am…