关于查询接口设计的一些小结

499 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

今天在学习尚硅谷的一个项目。其中的一个接口的实现过程让我很有感触,所以在此记录下来。这篇文章的内容如下:

  1. 在mybatisplus下如何封装查询条件
  2. 数据库表的实体类设计
  3. mybatis的分页查询

1.场景引入

这个接口的作用是查询订单列表。查询订单列表时可以进行分页查询和条件查询。我们数据库表如下所示:

订单表:

image.png 订单详情表:

image.png 具体的需求是在订单表里查询订单信息,而且要在订单详情表里查询对应的课程名称,最后一起返回。

2.订单表实体类的创建

各位同学看到这里,你会发现你这实体类和数据库表不对应,比如少了主键id字段数据插入时间数据更新时间字段。这就是我觉得巧妙的地方,我以前都没想过可以这样做,大家看下这个实体类继承了BaseEntity类。BaseEntity类里面写了通用的数据库表字段对应的属性。以后别的实体类就不需要写那么多字段了,直接继承了(如果各位同学是用代码生成器生成的代码,可以忽略这段内容)。

@Data
@ApiModel(description = "OrderInfo")
@TableName("order_info")
public class OrderInfo extends BaseEntity {

   private static final long serialVersionUID = 1L;

   @ApiModelProperty(value = "用户id")
   @TableField("user_id")
   private Long userId;

   @ApiModelProperty(value = "昵称")
   @TableField("nick_name")
   private String nickName;

   @TableField("phone")
   private String phone;

   @ApiModelProperty(value = "原始金额")
   @TableField("origin_amount")
   private BigDecimal originAmount;

   @ApiModelProperty(value = "优惠券减免")
   @TableField("coupon_reduce")
   private BigDecimal couponReduce;

   @ApiModelProperty(value = "最终金额")
   @TableField("final_amount")
   private BigDecimal finalAmount;

   @ApiModelProperty(value = "订单状态")
   @TableField("order_status")
   private String orderStatus;

   @ApiModelProperty(value = "订单交易编号(第三方支付用)")
   @TableField("out_trade_no")
   private String outTradeNo;

   @ApiModelProperty(value = "订单描述(第三方支付用)")
   @TableField("trade_body")
   private String tradeBody;

   @ApiModelProperty(value = "session id")
   @TableField("session_id")
   private String sessionId;

   @ApiModelProperty(value = "地区id")
   @TableField("province")
   private String province;

   @ApiModelProperty(value = "支付时间")
   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
   @TableField("pay_time")
   private Date payTime;

   @ApiModelProperty(value = "失效时间")
   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
   @TableField("expire_time")
   private Date expireTime;

}

BaseEntity类

大家看到这个类里面有数据库表字段对应的通用属性外,还有一个private Map<String,Object> param = new HashMap<>()。应为后面我们可以用这个字段来封装数据到继承了BaseEntity类的实体类里面。当然了你需要在这个的属性上加入@TableField(exist = false)。

@Data
public class BaseEntity implements Serializable {

    @ApiModelProperty(value = "id")
    @TableId(type = IdType.AUTO)
    private Long id;

    @ApiModelProperty(value = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField("create_time")
    private Date createTime;

    @ApiModelProperty(value = "更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField("update_time")
    private Date updateTime;

    @ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
    @JsonIgnore
    @TableLogic
    @TableField("is_deleted")
    private Integer isDeleted;

    @ApiModelProperty(value = "其他参数")
    @TableField(exist = false)
    private Map<String,Object> param = new HashMap<>();
}

3.MybatisPlus分页插件准备

这个步骤其实简单。首先创建一个配置类,在这个配置类里面进行简单设置即可,各位同学可以从这里进行简单复制。

@Configuration
@MapperScan("com.atguigu.ggkt.order.mapper")
public class OrderConfig {
    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

只要在配置类里面用注解指定mapper的位置和对应的Interceptor即可。

4.controller代码

controller代码其实很简单,就是写明要传的参数,要查询第几页的内容,每页显示几条记录,当然了还有查询条件。这我们可以将要查询的条件,封装成一个类,然后放到参数里,这里的条件类是OrderInfoQueryVo。 然后我们需要创建一个Page,因为这个对象是我们进行分页查询所需要的参数,在创建它的时候,你可以顺便将要查询的页数和每页的记录数传给它。

这里的Result是统一返回的数据格式。每个人习惯不同,你可以安装你喜欢的方式定义统一的返回数据格式。

在这里其实有个小技巧,就是如果你的controller不知道要返回什么数据的时候,可以设定他返回map集合。

@Api(tags = "订单管理")
@RestController
@RequestMapping(value="/admin/order/orderInfo")
public class OrderInfoController {

    @Autowired
    private OrderInfoService orderInfoService;

    //订单列表
    @ApiOperation(value = "获取订单分页列表")
    @GetMapping("{page}/{limit}")
    public Result listOrder(@PathVariable Long page,
                            @PathVariable Long limit,
                            OrderInfoQueryVo orderInfoQueryVo){
        //创建page对象
        Page<OrderInfo> pageParam = new Page<>(page,limit);
        Map<String,Object> map = orderInfoService.selectOrderInfoPage(pageParam,orderInfoQueryVo);
        return Result.ok(map);
    }

}

OrderInfoQueryVo类

@Data
public class OrderInfoQueryVo {
   
   @ApiModelProperty(value = "用户id")
   private Long userId;

   @ApiModelProperty(value = "nickName")
   private String nickName;

   @ApiModelProperty(value = "phone")
   private String phone;

   @ApiModelProperty(value = "订单状态")
   private Integer orderStatus;

   @ApiModelProperty(value = "订单交易编号(第三方支付用)")
   private String outTradeNo;

   @ApiModelProperty(value = "地区id")
   private String province;

   @ApiModelProperty(value = "创建时间")
   private String createTimeBegin;

   @ApiModelProperty(value = "创建时间")
   private String createTimeEnd;

}

5.service接口和service接口的实现类

public interface OrderInfoService extends IService<OrderInfo> {

    Map<String, Object> selectOrderInfoPage(Page<OrderInfo> pageParam, OrderInfoQueryVo orderInfoQueryVo);
}

因为我们是按条件分页查询,所以接口实现类里面会判断相应数据是否为空,最后添加到QueryWrapper来设置查询条件。

大家注意下,在BaseEntity类里的private Map<String,Object> param = new HashMap<>()在这里派上用场了。当我们查询了订单信息后,又根据id去查询订单详情表里的课程名字,查询到后,就封装到了实体类里面的map集合里面。

Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private OrderDetailService orderDetailService;

    //订单列表
    @Override
    public Map<String, Object> selectOrderInfoPage(Page<OrderInfo> pageParam, OrderInfoQueryVo orderInfoQueryVo) {
        //orderInfoQueryVo获取查询条件
        Long userId = orderInfoQueryVo.getUserId();
        String outTradeNo = orderInfoQueryVo.getOutTradeNo();
        String phone = orderInfoQueryVo.getPhone();
        String createTimeEnd = orderInfoQueryVo.getCreateTimeEnd();
        String createTimeBegin = orderInfoQueryVo.getCreateTimeBegin();
        Integer orderStatus = orderInfoQueryVo.getOrderStatus();

        //判断条件是否为空,不为空,进行条件封装
        QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();
        if (!StringUtils.isEmpty(userId)){
            wrapper.eq("user_id",userId);
        }
        if(!StringUtils.isEmpty(orderStatus)) {
            wrapper.eq("order_status",orderStatus);
        }
        if(!StringUtils.isEmpty(outTradeNo)) {
            wrapper.eq("out_trade_no",outTradeNo);
        }
        if(!StringUtils.isEmpty(phone)) {
            wrapper.eq("phone",phone);
        }
        if(!StringUtils.isEmpty(createTimeBegin)) {
            wrapper.ge("create_time",createTimeBegin);
        }
        if(!StringUtils.isEmpty(createTimeEnd)) {
            wrapper.le("create_time",createTimeEnd);
        }

        //调用实现条件分页查询
        Page<OrderInfo> pages = baseMapper.selectPage(pageParam, wrapper);
        long totalCount = pages.getTotal(); //总记录数
        long pageCount = pages.getPages();//总页数
        List<OrderInfo> records = pages.getRecords();//每页数据集合
        //订单里面包含详情内容,封装详情数据,根据订单id查询详情
        records.stream().forEach(item -> {
            this.getOrderDetail(item);
        });

        //所有需要数据封装map集合,最终返回
        Map<String,Object> map = new HashMap<>();
        map.put("total",totalCount);
        map.put("pageCount",pageCount);
        map.put("records",records);
        return map;
    }

    //查询订单详情数据
    private OrderInfo getOrderDetail(OrderInfo orderInfo) {
        //订单id
        Long id = orderInfo.getId();
        //查询订单详情
        OrderDetail orderDetail = orderDetailService.getById(id);
        if (orderDetail != null) {
            String courseName = orderDetail.getCourseName();
            orderInfo.getParam().put("courseName",courseName);
        }

        return orderInfo;
    }
}

因为持久层框架采用的是mybatisplus,所以mapper这里就不写了。

结果如下:

image.png

6.总结

如果下次有业务是做这样的接口,那么我们可以:

  1. 可以将实体类通用的部分单独做成一个类,其他实体类继承它即可。在这个通用的实体类里面我们可以定义一个map集合,以后封装数据方便。
  2. 如果持久层框架采用的是mybatisplus,那么分页插件大家可以直接复制上面的代码。