Java开发 - 分页查询初体验

Posted CodingFire

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java开发 - 分页查询初体验相关的知识,希望对你有一定的参考价值。

前言

在上一篇,我们对es进行了深入讲解,相信看过的小伙伴已经能基本掌握es的使用方法,我们知道,es主要针对的是搜索条件,在这方面es具有无可比拟的优势,但我们也会注意到,有时候搜索条件过于宽泛的时候,搜索结果集也将是非常庞大的,不仅服务器接口压力大,用户等的也很为难,为了解决这一问题,必须要对返回的数据进行处理,此时,分页就出现了,接下来,博主讲带两大家了解查询时的分页问题,并解决这个问题。

什么是分页查询

如上所述,分页查询是在返回数据量比较庞大时,为了能提高用户的体验,减少等待时间,而产生的一种数据分段方式,体现在展示层上就是数据的上拉加载,我们经常可以在购物类app上见到这样的设定,大多数情况下我们一页数据都为10条,大概是10条基本能满足查看一屏的需求和减少用户的等待时间的缘故,这是在移动端,在pc上可能略有不同,但基本都会以10的倍数存在。这便是分页查询。

分页查询的优点

分页查询的有点其实在上文中都已经说明,此处做一个总结,主要体现在三点上:

服务器

一次性查询所有信息,服务器压力过大,分页查询则可以降低服务器压力。

客户端

一次性显示所有信息,需要更多流量,加载更久,分页显示则可以解决这个问题。

用户体验

良好的用户体验可以让用户在应用上花费更多的时间,才能够提高购买率,也是服务商所希望的。最重要的一点,用户才不会放弃当前使用的应用。而且一般查询的数据都是会排序的,所以有价值的数据都会在前几页。

es的分页查询

前文es中没有对分页查询做说明,我们接上一篇的结尾,在项目中继续操作,但在说分页前,我们需要先对排序做一个了解,es的排序写法其实和SQL很相似,这在前文中我们已经有所见识,下面就一起来看看吧。

排序

排序和上一篇结尾的单条件/多条件查询都属于条件查询,我们在Repository类中来写一下怎么进行排序,在类中添加如下方法:

    // 排序查询
// 默认情况下,ES查询结果按score排序,如果想按其他的规则排序可以加OrderBy
// 和数据库一样,默认升序排序 Desc结尾会降序
    Iterable<People> queryItemsByNameMatchesOrderByBraveDesc(String name, Double brave);

接着进行测试,在测试类中添加如下测试方法:

    // 排序查询
    @Test
    void queryOrder()
        Iterable<People> items=peopleRepository
                .queryItemsByNameMatchesOrderByBraveDesc("西游");
        items.forEach(item -> System.out.println(item));
    

提示:在测试前,务必保证es处于启动状态,由于不是当天写的,所以es停止了,运行后发现报错,启动es后正常,下面看运行结果:

是按照我们想要的战力进行的排序,测试成功,你成功了吗?

来看看底层的代码逻辑:


### 单字段搜索排序
POST http://localhost:9200/peoples/_search
Content-Type: application/json


  "query": "match":  "name":  "西游" ,
  "sort":["brave":"desc"]

可以在条件中增加其他的条件进行尝试。

分页加排序

我们在使用es时用了SpringData框架,其支持分页查询,只需要修改参数和返回值就能实现自动分页的效果,这就帮我们省去了很多的代码和功夫,下面让我们来看看它是怎么分页的。

我们以刚刚的排序为例,在其基础上进行修改,额外增加一个方法:

    // 分页查询
// 当查询数据较多时,我们可以利用SpringData的分页功能,按用户要求的页码查询需要的数据
// 返回值修改为Page类型,这个类型对象除了包含Iterable能够包含的集合信息之外,还包含分页信息
    Page<People> queryItemsByNameMatchesOrderByBraveDesc(String name, Pageable pageable);

接着我们开始进行测试,由于我们数据量比较少,我们会把每页的数据设置的很少,看看代码怎么写:

// 分页查询
    @Test
    void queryPage()
        int pageNum=1;  //页码
        int pageSize=2; //每页条数
        Page<People> page= peopleRepository
                .queryItemsByNameMatchesOrderByBraveDesc(
                        "西游", PageRequest.of(pageNum-1,pageSize));
        page.forEach(item -> System.out.println(item));
        // page对象中还包含了一些基本的分页信息
        System.out.println("总页数:"+page.getTotalPages());
        System.out.println("当前页:"+page.getNumber());
        System.out.println("每页条数:"+page.getSize());
        System.out.println("当前页是不是首页:"+page.isFirst());
        System.out.println("当前页是不是末页:"+page.isLast());
    

我们在这里要注意分页的起始页,程序中我们第一位总是0开始的,但在生活中都是从1开始的,为了让主观上认为从1开始,我们在内部做减1操作。

运行测试代码,查看结果:

结果是按照排序后进行分页的,数据完全正确,测试成功。es的分页到这里就结束了,你学会了吗?新来的童鞋建议先看前一篇es,结合代码操作一遍,理解会更加的透彻。

数据库分页

PageHelper

SQL中我们通过limit关键字来进行分页查询,但却需要实时进行计算,在Spring Data中,我们通过框架帮我们解决了这个问题,PageHelper也是一个框架,它帮我们自动实现了分页效果,我们可以像Spring Data里那样,通过提供页码和数量来达到分页的目的。

所谓自动实现,也只是PageHelper在程序运行时,通过向SQL语句末尾添加limit的方式来实现分页,不要觉得讽刺,你当然可以选择自己来写,那是你的自由,我们使用框架的目的就是帮助我们降低开发的成本,提高开发的效率。

为了说明PageHelper的用法,我们还是在微服务的项目中进行同步讲解,以代码实战的形式进行。没有看过微服务篇的同学可以先去看看,这里要用到项目的工程,你也可以选择自建一个新的工程。但可能会略微有些麻烦。

添加依赖

我们在order模块进行代码的编写,首先添加依赖如下:

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

此依赖在添加senta依赖时已经添加过,我们要知道。

基本使用

持久层

我们在OrderMapper接口中添加查询的方法如下:

    @Select("select id,user_id,commodity_code,count,money from order_tbl")
    List<Order> findAllOrders();

需要注意,此方法不需要添加分页相关的参数,如limit,我们使用的框架会自动追加参数进去。他不区分SQL语句是写在注解中还是xml文件中。

业务逻辑层

在实现类OrderServiceImpl中添加此方法:

    // 分页查询所有订单的方法
// pageNum是要查询的页码
// pageSize是每页的条数
    public PageInfo<Order> getAllOrdersByPage(Integer pageNum, Integer pageSize)

        // 利用PageHelper框架的功能,指定分页的查询的页码和每页条数
        // pageNum为1时,就是查询第一页,和SpringData的分页不同(SpringData分页0表示第一页)
        PageHelper.startPage(pageNum,pageSize);
        // 调用查询所有订单的方法
        // 因为上面设置了分页查询的条件,所以下面的查询就会自动在sql语句后添加limit关键字
        // 查询出的list就是需要查询的页码的数据
        List<Order> list=orderMapper.findAllOrders();
        // 我们完成了分页数据的查询,但是当前方法要求返回分页信息对象PageInfo
        // PageInfo中可以包含分页数据和各种分页信息,这些信息都是自定计算出来的
        // 要想获得这个对象,可以在执行分页查询后实例化PageInfo对象,所有分页信息会自动生成
        return new PageInfo<>(list);
    

PageInfo对象既包含查询数据结果,又包含分页信息,我们来看看该类中有哪些属性:

//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的行数量
private int size;
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总页数
private int pages;
//前一页页号
private int prePage;
//下一页页号
private int nextPage;
//是否为第一页
private boolean isFirstPage;
//是否为最后一页
private boolean isLastPage;
//是否有前一页
private boolean hasPreviousPage;
//是否有下一页
private boolean hasNextPage;
//导航条中页码个数
private int navigatePages;
//所有导航条中显示的页号
private int[] navigatepageNums;
//导航条上的第一页页号
private int navigateFirstPage;
//导航条上的最后一页号
private int navigateLastPage;

根据需要选择需要的参数。

控制器层

业务逻辑层完成后,就需要在控制器层通过接口来调用接口方法,在OrderController类中添加如下控制器方法:

    @GetMapping("/page")
    @ApiOperation("分页查询订单")
    @ApiImplicitParams(
            @ApiImplicitParam(value = "页码",name="pageNum",example = "1"),
            @ApiImplicitParam(value = "每页条数",name="pageSize",example = "10")
    )
    public JsonResult<PageInfo<Order>> pageOrders(Integer pageNum, Integer pageSize)
        // 分页调用
        PageInfo<Order> pageInfo=orderService.getAllOrdersByPage(pageNum,pageSize);
        return JsonResult.ok("查询完成",pageInfo);
    

你可以从方法中看到我们之前写的方法的影子,只是在参数方面我们做了一些调整,加入了分页相关的信息。由于我们这里没有写接口方法,所以为了调用实现类中的方法,我们需要修改此类中IOrderService为OrderServiceImpl,直接调用实现方法。

测试

在这里,启动nacos和seata,如果es还开启着,可以关闭了,博主的电脑都开始烫了。接着运行order

模块,额,报错?看看啥报错:

java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not all

以前都没报这个错啊,奇怪了,数据库也没升级,经查询,通过在spring.datasource.url后添加allowPublicKeyRetrieval=true可解决此问题。注意添加的格式:

    url: jdbc:mysql://localhost:3306/cloud_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&allowPublicKeyRetrieval=true

启动成功后,我们在浏览器输入在线文档的地址:http://localhost:20002/doc.html

打开此页面:

点击发送按钮,可看到如下数据:

博主的数据库有11条数据,第一页给了10条数据,将页码改为2后,点击发送:

控制台中也打印出了自动追加limit的SQL语句:

==> Preparing: select id,user_id,commodity_code,count,money from order_tbl LIMIT ?, ?
==> Parameters: 10(Long), 10(Integer)

这里只返回了一条数据,就代表我们的测试已经成功了。但,我们注意到,上面的代码中还是存在一定问题,因为我们没有使用接口进行调用,而是直接调用了实现类方法,这是不符合规则的。原因可以看下方数据返回的内容。下面,我们将来完善这个步骤。

数据返回

分页查询我们在上面的方法中使用的PageInfo作为返回值,这是不合适的,因为使用此类,这个类就会出现在任何调用这个方法的模块,这些模块都将添加PageHelper的依赖,而且我们也不方便来做一些自定义的东西,为了解决此问题,我们需要在commons模块中添加一个返回类来代替PageInfo。

添加依赖

所以在PageInfo模块中,我们添加依赖如下:

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>

添加替代类

接着我们在restful包中新建一个JsonPage类,将PageInfo类封装进去:

package com.codingfire.cloud.commons.restful;

import com.github.pagehelper.PageInfo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

// 通用支持分页查询的结果对象类型
@Data
public class JsonPage<T> implements Serializable 

    // 按照实际需求,定义这个类中的属性
    @ApiModelProperty(value = "当前页码",name = "pageNum")
    private Integer pageNum;
    @ApiModelProperty(value = "每页条数",name = "pageSize")
    private Integer pageSize;
    @ApiModelProperty(value = "总条数",name = "totalCount")
    private Long totalCount;
    @ApiModelProperty(value = "总页数",name = "totalPages")
    private Integer totalPages;
    // 声明一个属性,来承载查询到的分页数据结果
    @ApiModelProperty(value = "分页数据",name = "list")
    private List<T> list;

    // 所有属性写完了,下面要编写将其他框架的分页结果转换成当前类对象的方法
    // SpringDataElasticsearch或PageHelper等具有分页功能的框架,均有类似PageInfo的对象
    // 我们可以分别编写方法,将它们转换成JsonPage对象,我们先只编写PageHelper的转换
    public static <T> JsonPage<T> restPage(PageInfo<T> pageInfo)
        // 下面开始将pageInfo对象的属性赋值给JsonPage对象
        JsonPage<T> result=new JsonPage<>();
        result.setPageNum(pageInfo.getPageNum());
        result.setPageSize(pageInfo.getPageSize());
        result.setTotalCount(pageInfo.getTotal());
        result.setTotalPages(pageInfo.getPages());
        result.setList(pageInfo.getList());
        // 返回赋值完毕的JsonPage对象
        return result;
    



修改业务逻辑层

IOrderService接口类中添加新接口方法:

    // 分页查询所有订单的方法
    JsonPage<Order> getAllOrdersByPage(Integer pageNum, Integer pageSize);

接着在实现类OrderServiceImpl中修改原来的实现方法如下:

public JsonPage<Order> getAllOrdersByPage(Integer pageNum, Integer pageSize)

    // 利用PageHelper框架的功能,指定分页的查询的页码和每页条数
    // pageNum为1时,就是查询第一页,和SpringData的分页不同(SpringData分页0表示第一页)
    PageHelper.startPage(pageNum,pageSize);
    // 调用查询所有订单的方法
    // 因为上面设置了分页查询的条件,所以下面的查询就会自动在sql语句后添加limit关键字
    // 查询出的list就是需要查询的页码的数据
    List<Order> list=orderMapper.findAllOrders();
    // 我们完成了分页数据的查询,但是当前方法要求返回分页信息对象PageInfo
    // PageInfo中可以包含分页数据和各种分页信息,这些信息都是自定计算出来的
    // 要想获得这个对象,可以在执行分页查询后实例化PageInfo对象,所有分页信息会自动生成
    //     ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    return JsonPage.restPage(new PageInfo<>(list));

修改控制器层

此时,控制器层因为上面的代码修改已经报错了,然后修改控制器层方法,有两处:

第一处是OrderServiceImpl:

    private OrderServiceImpl orderService;
    private IOrderService orderService;

其实就是修改回来,第一行改回第二行。

第二处是原方法修改返回值:

    public JsonResult<JsonPage<Order>> pageOrders(Integer pageNum, Integer pageSize)
        // 分页调用
        JsonPage<Order> jsonPage=orderService.getAllOrdersByPage(pageNum,pageSize);
        return JsonResult.ok("查询完成",jsonPage);
    

一路修改,到这里就结束了。我们可以重新order模块,通过在线文档来进行测试,发现测试结果是一样的,这就可以了,测试成功,这一步是对返回值做了处理,使我们可以自定义返回值的对象,是代码更加的灵活,你可以对比修改前后返回值的结构。

结语

分页查询在开发中的使用非常频繁,就是再小的项目也离不开分页功能,虽然不一定使用es,但分页绝对会用,学完此篇,分页功能你就基本掌握了,剩下的就是在实践中使用此功能,相信你一定已经学会了。另外,分页功能还不算难,和es比起来算是比较基础的功能了,只是涉及es的分页,才展开讲了一下,这是每一个后台开发者都必须会的内容,大家有时间多做练习,自己进行尝试,才能够掌握得更好。又到了和大家说再见的时候,觉得不错,就三连(点赞,收藏,评论)支持一下吧。

SSH初体验系列--Hibernate--3--单值与分页查询

前言

  查询可以按结果集分2类:单个结果 和 数组 ; 其中,返回数组,在这个 数据库数据量随随便便就能上几十万的互联网时代大背景下,常常需要做分页处理, 所以这里就说一下单值和分页, 算是对上一篇"curd"的一些延伸

一.单值查询

    比如,我们需要查询数据库,统计一下app的当前注册人数, 此时我们知道返回结果必定是"单行单列"的一个数值,那么可以用这种单值查询方式;

    使用uniqueResult()方法,它返回一个java.lang.Object对象,并且能保证返回的一定是一个单值对象,否则就会报错;如果返回0个对象,也会报错;所以我们需要放到try-catch块中

        Session session=null;
        try{
            session= HibernateUtil.openSession();
            String hql="select count(*) from Feedback";
           long count= (long) session.createQuery(hql).uniqueResult();
            System.out.println("get data ok");
        }catch(Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }finally {
            session.close();
        }

二.分页查询

    在这个互联网时代,动辄几十万的数据也是非常平常的事情了,每次查询全部加载是很不现实的。因此,做分页查询,不仅节省db性能,带宽,流畅度上来了还能提升用户体验.

    hibernate的Query类下有两个方法可以帮我们达到分页查询目的:setFirstResult(int num)和setMaxResults(int size);  前者的num是查询的起始记录数,比如从第3条开始查询,就是setFirstResult(3); 后者的size是查询结果集的大小,比如获取10条记录,那就是setMaxResults(10);

            //分页查询
            String hql="from Feedback order by id desc";
            List<Feedback> feedbacks=session.createQuery(hql).setFirstResult(1).setMaxResults(2).list();

    其中: setFirstResult(int num)的num仍旧是由0开始的;

    于是,配合着pageIndex,pageCount,很容易就可以得到分页请求代码,在上面代码的基础上修改一下就行:

            //分页查询
            String hql="from Feedback order by id desc";
            
            int pageIndex=2,pageCount=10;
            
            List<Feedback> feedbacks=session.createQuery(hql).setFirstResult((pageIndex-1)*pageCount).setMaxResults(pageCount).list();

 

  

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利;

本文出自:博客园--别问是谁

以上是关于Java开发 - 分页查询初体验的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis分页插件:PageHelper和PageInfo

关于PageHelper分页问题。我用PageHelper分页查询后对查询的结果进行添加删除操作后,怎样保证pageInfo

问一个Mybatis分页问题,Page和PageInfo的区别

Java开发 - Elasticsearch初体验

Java开发 - Elasticsearch初体验

使用 TypeORM getManyWithCount 如何生成 PageInfo 进行分页