用惯了框架的分页API?今天教你手写分页查询

用惯了框架的分页API?今天教你手写分页查询然后由于除不尽的原因,我们需要事先将dataAmount变换成浮点型数据,然后这样我们就可以得到一个double类型的数据。

欢迎大家来到IT世界,在知识的湖畔探索吧!

用惯了框架的分页API?今天教你手写分页查询

概要

分页功能是比较常见的基础功能,虽然比较简单,而且也有现成的API,初学Java的小白往往不知道其中的奥秘,今天就来手写一个分页查询,让我们慢慢的品味其中的奥秘吧。

逻辑描述

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

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

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

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

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

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

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

功能实现

定义返回Wrapper类型

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

public class PageForDataList<T> { /** 每页条数*/ private int pageSize; /** 数据总条数*/ private int dataAmount; /** 总页数*/ private int pageAmount; /** 请求页数*/ private int wantPage; /** 单页记录列表list*/ private List<T> dataList; // 构造器...... // getter、setter....... } 

欢迎大家来到IT世界,在知识的湖畔探索吧!

设置接口参数

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

欢迎大家来到IT世界,在知识的湖畔探索吧! @GetMapping(value = "/special_price/ticket/list") public PageForDataList<SpecialTicketPrice> specialPriceList( int pageSize, int wantPage, String scenicName, String scenicLocation, Integer rmdStatus, Double rateStart, Double rateEnd) { return sprSvc.ticketList(pageSize, wantPage, scenicName, scenicLocation, rmdStatus, rateStart, rateEnd); } 

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

DAO层的分页实现

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

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

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

欢迎大家来到IT世界,在知识的湖畔探索吧! 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 "); // 查询总条数sql String 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,返回给页面即可。

最终效果展示

用惯了框架的分页API?今天教你手写分页查询

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

用惯了框架的分页API?今天教你手写分页查询

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

—欢迎关注【Java圣斗士】,我是你们的小可爱(✪ω✪) Morty—

—专注IT职场经验、IT技术分享的灵魂写手—

—每天带你领略IT的魅力—

—期待与您陪伴!—

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/74656.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信