我是靠谱客的博主 和谐水杯,最近开发中收集的这篇文章主要介绍JSD-2204-(业务逻辑开发)-开发分类功能-分页查询-Day081.开发分类功能2.分页查询,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.开发分类功能

1.1数据导入

在给大家提供的csmall-jsd2203项目的doc文件夹下的sql文件夹中

有多个sql语句文件

分别去运行它们,我们可以获得酷鲨商城前台的数据库信息了

我们每个微服务项目原则上只操作少于一个数据库

1.2分类功能实现逻辑

我们数据库mall_pms的category表使用自关联实现了三级分类

当前酷鲨商城项目使用固定的三级分类

1.从数据库中查询出所有分类信息,一次性全查

2.构建分类信息的父子结构,实现查询返回父子结构的分类信息

3.将查询到的结果保存在Redis中,以备后续用户直接获取

代码中要判断Redis中是否包含全部分类数据,不包含的话做上面操作

包含分类数据的话直接获得之后返回

1.3业务分析

查询全部分类的业务重点在构建三级分类树结构

我们需要将从数据库中查询出的分类对象构成下面的结构

[
    {id:1,name:"手机/运行商/数码",parentId:0,depth:1,children:[
        {id:2,name:"手机通讯",parentId:1,depth:2,children:[
            {id:3,name:"智能手机",parentId:2,depth:3,children:null},
            {id:4,name:"非智能手机",parentId:2,depth:3,children:null}
        ]}
    ]},
    {id:5,name:"电脑/办公",parentId:0,depth:1,children:[....]}
]

上面是我们需要获得的对象的结构

可以理解为下图

在数据库mall_pms中

有pms_category表,这个表就是保存全部分类信息的表格

id:主键

name:显示在页面上的分类名称

parentId:父分类的id 如果是一级分类父分类id为0

depth:分类深度,当前项目就是3级分类,123 分别代表它的等级

keyword:搜索关键字

sort:排序依据 正常查询时,根据此列进行排序,数字越小越出现在前面(升序)

icon:图标地址

enable:是否可用

isparent:是否为父分类 0 假 1真

isdisplay:是否显示在导航栏 0不显示 1显示

随堂更新git地址:

jtzhanghl/csmall-repo-class

1.4实施开发

在csmall-front-webapi项目中开发

创建service.impl包

包中编写业务逻辑层实现类 实现IFrontCategoryService

@DubboService
@Service
@Slf4j
public class FrontCategoryServiceImpl implements IFrontCategoryService {

    // 开过程中使用Redis的规范:为了降低Redis的Key拼写错误的风险,我们会定义常量使用
    public static final String CATEGORY_TREE_KEY="category_tree";

    // 当前front模块没有连接数据库的操作,所有数据均来源于Dubbo调用product模块
    // 所有要消费product模块具备对应功能的接口
    @DubboReference
    private IForFrontCategoryService dubboCategoryService;
    // 操作Redis的对象
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public FrontCategoryTreeVO categoryTree() {
        // 我们先检查Redis中是否包含已经查询出的三级分类树
        if(redisTemplate.hasKey(CATEGORY_TREE_KEY)){
            // redis中已经包含了三级分类树,直接获取后返回即可
            FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
                    (FrontCategoryTreeVO<FrontCategoryEntity>)
                        redisTemplate.boundValueOps(CATEGORY_TREE_KEY).get();
            // 千万别忘了返回!
            return treeVO;
        }
        // Redis中没有三级分类树信息,表示当前请求可能是第一次请求
        // dubbo调用查询pms数据库所有分类对象的方法
        List<CategoryStandardVO> categoryStandardVOs=
                            dubboCategoryService.getCategoryList();
        // 需要将没有关联关系的分类列表CategoryStandardVO类型
        // 转换为具备关联关系的分类树列表FrontCategoryEntity类型
        // 自定义一个转换三级分类树的方法,减少当前业务代码的冗余度
        FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
                initTree(categoryStandardVOs);
        // 上面方法完成了转换,已经获得了最后要返回的treeVO数据
        // 但是为了下次访问更方便,我们项目设计将它保存在Redis中
        redisTemplate.boundValueOps(CATEGORY_TREE_KEY)
                        .set(treeVO,1, TimeUnit.MINUTES);
        // 上面定的时间是针对学习使用的,正常开发分类数据一般都24小时以上
        // 这里也得返回!!!treeVO
        return treeVO;
    }

    private FrontCategoryTreeVO<FrontCategoryEntity> initTree(List<CategoryStandardVO> categoryStandardVOs) {
        // 第一部分,确定所有分类对象的父分类
        // 声明一个Map,这个Map的key是分类对象的父Id,value是这个父分类Id下的所有子分类对象
        // 一个父分类Id可以包含多个子分类对象,所以value是个list
        Map<Long,List<FrontCategoryEntity>> map=new HashMap<>();
        log.info("当前分类对象总数为:{}",categoryStandardVOs.size());
        // 遍历categoryStandardVOs
        for(CategoryStandardVO categoryStandardVO: categoryStandardVOs){
            // categoryStandardVO是没有children属性的,不能保存分类关系
            // 所以我们先要把它转换为能够保存父子关系的FrontCategoryEntity
            FrontCategoryEntity frontCategoryEntity=new FrontCategoryEntity();
            // 将同名属性赋值到frontCategoryEntity对象中
            BeanUtils.copyProperties(categoryStandardVO,frontCategoryEntity);
            // 因为后面会频繁使用父分类id,所以现在取出
            Long parentId=frontCategoryEntity.getParentId();
            // 向map中新增当前分类对象到对应的map的key中
            // 要先判断这个key是否已经存在
            if(map.containsKey(parentId)){
                // 如果已经存在这个key,就只需要将新对象保存在这个key对应的value的list中
                map.get(parentId).add(frontCategoryEntity);
            }else{
                // 如果当前map没有这个key
                // 我们要创建一个List对象,保存当前分类对象后,最后当做map的value
                List<FrontCategoryEntity> value=new ArrayList<>();
                value.add(frontCategoryEntity);
                // map中使用put方法新增元素,parentId为key,list为值
                map.put(parentId,value);
            }
        }
        // 第二部分,将子分类对象关联到父分类对象的children属性中
        // 第一部分中,我们确定了每个父分类下的所有子分类
        // 下面我们从一级分类来开始,将所有对应的子分类赋值到当前父分类的children属性中
        // 我们项目设计数据库中父分类id为0,是一级分类,所以我们先获得所有一级分类对象
        List<FrontCategoryEntity> firstLevels=map.get(0L);
        // 判断当前一级分类是否为null
        if (firstLevels==null || firstLevels.isEmpty()){
            throw new CoolSharkServiceException(
                    ResponseCode.INTERNAL_SERVER_ERROR,"当前数据没有根分类");
        }
        //遍历所有一级分类对象
        for(FrontCategoryEntity oneLevel:firstLevels){
            // 确定了一级分类对象,可以根据当前一级分类的id,获得它包含的二级分类
            Long secondLevelParentId=oneLevel.getId();
            // 获得当前分类的所有子分类对象
            List<FrontCategoryEntity> secondLevels=map.get(secondLevelParentId);
            // 判断secondLevels是否为空
            if(secondLevels==null || secondLevels.isEmpty()){
                log.warn("当前分类没有二级分类内容:{}",secondLevelParentId);
                // 如果当前一级分类没有二级分类对象,跳过本次循环,继续其它一级分类的遍历
                continue;
            }
            // 遍历二级分类集合
            for(FrontCategoryEntity twoLevel : secondLevels){
                // 二级分类的对象的id是三级分类的父id
                //                              ↓↓↓↓↓↓↓
                Long thirdLevelParentId=twoLevel.getId();
                // 根据这个二级父分类id获得所有该分类下的三级分类对象集合
                List<FrontCategoryEntity> thirdLevels=map.get(thirdLevelParentId);
                // 判断三级分类集合是否为空
                if(thirdLevels==null || thirdLevels.isEmpty()){
                    log.warn("当前分类没有三级分类内容:{}",thirdLevelParentId);
                    continue;
                }
                // 将三级分类对象集合赋值到二级分类对象的children属性中
                twoLevel.setChildrens(thirdLevels);
            }
            // 将二级分类对象集合赋值到一级分类对象的children属性中
            oneLevel.setChildrens(secondLevels);
        }
        // 到此为止,所有对象的父子分类关系都已经构建完成
        // 最后要按照方法要求的返回值来返回
        FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
                new FrontCategoryTreeVO<>();
        // 最后将包含所有分类对象的一级分类树集合赋值到该对象中
        treeVO.setCategories(firstLevels);
        // 千万别忘了返回!!! treeVO
        return treeVO;
    }
}

 

创建控制层

controller包

CategoryController类

代码如下

@RestController
@RequestMapping("/front/category")
@Api(tags = "前台分类查询")
public class CategoryController {

    @Autowired
    private IFrontCategoryService frontCategoryService;

    @GetMapping("/all")
    @ApiOperation("查询所有三级分类树")
    public JsonResult<FrontCategoryTreeVO<FrontCategoryEntity>> getTreeVO(){
        FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
                frontCategoryService.categoryTree();
        return JsonResult.ok(treeVO);
    }

}

启动nacosseataredis

先启动生产者product后启动消费者front

访问

localhost:10004/doc.html

2.分页查询

2.1分页查询的优点

所谓分页,就是查询结果数据较多时,采用按页显示的方法,而不是一次性全部显示

分页的优点:

  1. 服务器:一次性查询所有信息,服务器压力大,分页查询服务器压力小
  2. 客户端:一次性显示所有信息,需要更多流量,加载时间也会更长,分页显示没有这个问题
  3. 用户体验上:一般最有价值的信息都会在前几页显示,也方便用户记忆,多查询出来的数据使用几率很低

实现分页查询需要我们开发过程中多几个步骤

2.2PageHelper实现分页查询

我们可以使用sql语句中添加limit关键字的方法实现分页查询

但是查询分页内容时,我们要自己计算相关的分页信息和参数

limit 0,10 limit 10,10

分页逻辑无论什么业务都是类似的,所以有框架帮助我们高效实现分页功能

PageHelper框架可以实现我们提供页码和每页条数,自动实现分页效果,收集分页信息

PageHelper的分页原理就是在程序运行时,在sql语句尾部添加limit关键字,并按照分页信息向limit后追加分页数据

要想使用,首先还是添加依赖

我们在之前搭建的微服务项目中先编写学习,建议使用csmall-order模块

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

在添加seata支持时已经添加了pagehepler依赖

2.3PageHelper的基本使用

2.3.1先编写持久层

我们使用csmall-order-webapi模块来完成分页的测试

首先编写分页的持久层mapper,持久层功能是全查所有订单信息

OrderMapper添加方法

// 分页查询全部订单
// PageHelper框架是自动在sql语句后添加limit关键字实现分页的
// 因为这个特性,我们编写的查询语句,和不分页时是一样的
@Select("select id,user_id,commodity_code,count,money from order_tbl")
List<Order> findAllOrders();

注意这个方法并不需要任何分页的参数或返回值,sql也不需要编写limit

都是在业务逻辑层中由PageHelper框架处理的

2.3.2编写业务逻辑层

下面就转到业务逻辑层实现类,先编写一个方法使用PageHelper的功能

先不用写接口,直接在业务逻辑层中写方法

OrderServiceImpl添加方法

// 分页查询所有订单的方法
// page 是页码
// pageSize 是每页条数
public PageInfo<Order> getAllOrdersByPage(Integer page,Integer pageSize){
    // PageHepler框架实现分页最核心的代码就是在运行要分页的查询语句之前
    // 通过框架给定的方法设置要分页查询的要求(第几页,每页多少条)
    // 参数page和SpringData框架的设计不同,page为1就是第一页,page为2就是第二页
    PageHelper.startPage(page,pageSize);
    // 当上面设置完分页要求后,下面紧随的下一次查询,
    // 就会自动在sql语句末尾添加limit关键字,limit后面的值就是按page,pageSize得出的
    List<Order> list= orderMapper.findAllOrders();
    // list并不是全部的订单的集合,而是按上面分页条件查询出的分页数据
    // 在返回时不直接返回list对象,而是返回PageHelper框架提供的PageInfo类型对象
    // 这个对象可以保存list集合的同时,还能自动计算分页信息

    return new PageInfo<>(list);
}

PageInfo对象既包含查询数据结果,又包含分页信息

数据结构如下图

附: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;

2.3.3编写控制层

在OrderController类中添加调用分页方法

@GetMapping("/page")
@ApiOperation("分页查询所有订单")
@ApiImplicitParams({
        @ApiImplicitParam(value = "页码",name = "page" ,example = "1"),
        @ApiImplicitParam(value = "每页条数",name = "pageSize" ,example = "6")
})
public JsonResult<PageInfo<Order>> pageOrders(
                                Integer page,Integer pageSize){
    //  执行分页查询的业务逻辑层
    PageInfo<Order> pageInfo=orderService.getAllOrdersByPage(page,pageSize);
    return JsonResult.ok("查询完成",pageInfo);

}

启动NacosSeata

启动order

进行knife4j测试http://localhost:20002/doc.html#/home

可以观察控制台输出的运行的sql语句(会自动添加limit关键字)

2.4使用JsonPage返回结果

当前我们分页查询返回的类型是PageInfo

如果用这个类型来做业务逻辑层的返回值,当当前方法作为dubbo生产者对外提供服务时

消费者调用该服务需要使用PageInfo类型对象来接收,这样要求消费者也添加PageHepler依赖,这是不合理的

所以我们设计在commons模块中,添加一个专门用于返回分页结果的类JsonPage,代替PageInfo

这样当前微服务项目中,所有分页或类似的操作,就都可以使用这个类了

例如之前SpringDataElasticsearch框架也支持分页,返回类型为Page,它也可以替换为JsonPage

因为需要在commons模块中使用PageInfo类型,所以commons模块要添加pageHelper的依赖

<!--  为了将PageHelper框架中分页查询返回值PageInfo转换为JsonPage,添加的依赖  -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

在restful包中新建一个JsonPage类

代码如下

// 通用的返回各种类型分页结果的信息类
@Data
public class JsonPage<T> implements Serializable {

    // 根据实际需求,定义需要的分页信息
    // 实际开发中可能较多,我们这里就声明4个基本的
    @ApiModelProperty(value = "总页数",name = "totalPages")
    private Integer totalPages;
    @ApiModelProperty(value = "总条数",name = "totalCount")
    private Long totalCount;
    @ApiModelProperty(value = "当前页码",name = "page")
    private Integer page;
    @ApiModelProperty(value = "每页条数",name = "pageSize")
    private Integer pageSize;
    // 如果需要再添加其它属性即可

    // 除了分页信息,还有查询出的分页数据
    @ApiModelProperty(value = "分页数据",name = "list")
    private List<T> list;

    // 上面定义了所有分页数据需要的属性
    // 下面可以编写一个将PageInfo类型转换为JsonPage类型的方法
    // 如果需要将其它框架的分页对象转换,例如SpringData的Page类,那么就再编写新的方法即可
    public static <T> JsonPage<T> restPage(PageInfo<T> pageInfo){
        // 开始进行转换,基本思路是将pageInfo对象中的数据赋值给JsonPage对象
        JsonPage<T> result=new JsonPage<>();
        // 赋值分页信息
        result.setTotalPages(pageInfo.getPages());
        result.setTotalCount(pageInfo.getTotal());
        result.setPage(pageInfo.getPageNum());
        result.setPageSize(pageInfo.getPageSize());
        //  赋值分页数据
        result.setList(pageInfo.getList());
        // 别忘了返回
        return result;

    }
}

下面去使用这个类

csmall-order-service业务逻辑层接口项目添加方法

返回值使用JsonPage

// 返回JsonPage类型的分页查询全部订单方法
JsonPage<Order> getAllOrdersByPage(Integer page,Integer pageSize);

csmall-order-webapi项目实现类中进行修改

//     ↓↓↓↓↓↓↓↓
public JsonPage<OrderTb> getAllOrdersByPage(Integer pageNum, Integer pageSize){

    PageHelper.startPage(pageNum,pageSize);

    List<OrderTb> list= orderMapper.findAllOrders();

    //     ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    return JsonPage.restPage(new PageInfo<>(list));
}

业务逻辑层返回值的修改影响控制器方法的调用

再去修改OrderController中方法调用的位置

@Autowired
//      ↓↓↓↓↓↓↓↓↓↓↓↓
private IOrderService orderService;

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

保证启动NacosSeata

重启order测试

能出现查询结果即可

最后

以上就是和谐水杯为你收集整理的JSD-2204-(业务逻辑开发)-开发分类功能-分页查询-Day081.开发分类功能2.分页查询的全部内容,希望文章能够帮你解决JSD-2204-(业务逻辑开发)-开发分类功能-分页查询-Day081.开发分类功能2.分页查询所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部