小议mybatis plus相比传统mybatis手写SQL的好处

谭文涛 2020-12-04 浏览量:20596

一、 场景描述

假设有两张表:一张商品表、一张订单表,具体表的字段如下:

Snip20201204_3.png

商品表和订单表是一对多的关系,一个商品可以有多个订单。现有如下需求:

1、 发送通知需求:某一特定类别的商品,购买成功后需要发送通知给下单的客户,下单后默认通知状态为未发送,发送成功标记已发送。

2、 查询需求:查询某一用户的订单列表,列出订单信息和订单的商品信息。供给用户购买记录页面呈现使用。

二、 发送通知需求(传统mybatis写法 和 mybatis plus 对比)

传统的mybatis做ORM映射工具并手写sql的时代,常规的写法基本上会是在dao层和servise层按此需求场景实现相应方法。

1、 发送通知需求:传统手写sql的时代dao层基本上会实现两个方法:

(1)“查询某一产品类别下尚未推送通知的订单”方法:用订单表关联产品表查询出某一产品类别下,尚未推送的订单

@Select(" SELECT o.id,o.order_code AS orderCode,o.notify_status AS notifyStatus,o.product_id AS productId 
          FROM product p  " +
        " INNER JOIN order o ON o.product_id=p.product_id " +
        " WHERE p.product_type_id=#{productType} and o.notify_status = 0 order by o.id  ")
List<Order> getNeedNotifyOrderBYProducType(@Param("productType") Integer productType);

(2)修改订单推送状态方法:

@Update("update order set notify_status=#{notifyStatus},update_time=NOW() " +
        "WHERE id = #{id}")
Integer updateStatus(@Param("id") Integer id,@Param("notifyStatus") Integer notifyStatus);

注:将sql写在xml文件里的方式同理 然后发送通知这个需求基本就能实现了,写个定时任务每间隔多久运行一次,先取出待推送的订单,然后调用逐一调推送方法,再修改订单状态。(或者是批量的:先批量推送,然后批量修改订单状态)

但是随着项目持续开展,需求的不断演进,你会发现这两个方法的代码就只适用于这个场景而已,这里称之为“单一场景方法”。而且项目里这种单一场景的方法会越来越多、越来越多。打开dao层的文件一看“一堆方法”。今天订单关联商品写个join的关联查询语句,明天订单关联别的又写一个join的关联查询。今天修改订单推送状态写个update方法,明天修改订单别的状态又写个update方法。项目越来越臃肿,新人上手的可维护难度变得很大。 也许有人会说,我们可以写一个统一的update方法,供service层调用。传进来实体类即可,根据主键ID修改相应数据。如下面:

this.orderDao.updateByPrimaryKey(order);

这样做隐患更大,因为我们上面的select语句是手写的sql ,字段都是手写的,因此如果需求改变了,订单表增加了几个字段,那么这些手写的select语句的sql就得从头到尾全都改一遍,否则这些sql查询出的实体数据,再执行updateByPrimaryKey方法就会把新增的字段修改为空。因此一个看似加个字段的简单需求,研发改动点会很多,测试的验证点也会很多。

2、 发送通知需求:mybatis plus时代,利用mybatis plus + Lambda表达式就能轻松实现,具体代码如下:

//第一步根据类别查询该类别下的商品
QueryWrapper<ProductEntity> productQueryWrapper = new QueryWrapper<>();
productQueryWrapper.eq("product_type_id", productTypeId);
List<ProductEntity> productList = productService.list(productQueryWrapper);

//第二步 用lambda表达式取出商品ID的集合
if(productList!=null && productList.size()>0){
    List<Integer> proudctIdList = productList.stream().map(p -> p.getProductId()).collect(Collectors.toList());

//第三步 查询该商品类别下的 未推送通知的订单列表
    QueryWrapper<OrderEntity> orderEntityQueryWrapper = new QueryWrapper<>();
    orderEntityQueryWrapper.in("product_id",proudctIdList);
    orderEntityQueryWrapper.eq("notify_status", StateTypeEnum.invalid.value());
    orderEntityQueryWrapper.orderByDesc("id");
    List<OrderMallEntity> orderList = orderService.list(orderEntityQueryWrapper);
}

最后处理完发送通知后,调用save方法修改订单状态

order.setNotifyStatus(StateTypeEnum.effective.value());
order.setUpdateTime(new Date());
orderService.saveOrderMall(order);

用mybatis plus QueryWrapper实现的好处就是:

(1) 全业务处理流程没有手写的SQL,dao层不会有过多的方法代码;

(2) 查询和修改方法由于都是基于实体映射的,因此即使日后扩展字段也不会有问题

三、 查询需求(传统mybatis写法 和 mybatis plus 对比)

需求:查询某一用户的订单列表,列出订单信息和订单的商品信息。 1、 查询需求:传统手写sql的时代dao层基本上会实现一个方法: 用订单表去关联产品表,查询出前端展示所需要的全部字段信息。

@Select(" SELECT o.id,o.order_code AS orderCode,o.notify_status AS notifyStatus,o.product_id AS productId " +
        "      ,p.product_name AS productName,p.product_type_id AS productType,p.product_img AS  productImg " +
        "      ,p.product_price AS  productPrice " +
        " FROM product_summary p  " +
        " INNER JOIN order_mall o ON o.product_id=p.product_id " +
        " WHERE  o.user_id=#{userId} " +
        " ORDER BY o.create_time desc ")
List<MallOrderDto> getOrdersByUserId(@Param("userId") Integer userId);

这种“单一场景”写法依然程序的健壮性很差,举个例子:某一天产品同事告诉你说咱们的商品需要增加一个“双十一”活动标签的概念。商品列表页、商品详情页、用户购买记录和订单详情页等等,只要涉及商品的地方,是“双十一”的商品都需要加上这个标签。 一听到这个需求,第一意识就是需要在商品表里加个字段,然后将所有涉及产品的实体类扩展这个属性,并且将所有dao层的查询sql都修改一遍,加上这个字段。

2、 查询需求:mybatis plus时代,利用mybatis plus + Lambda表达式就能轻松实现,具体代码如下:

//第一步根据 用户查询该用户的订单列表
QueryWrapper<OrderEntity> orderEntityQueryWrapper = new QueryWrapper<>();
orderEntityQueryWrapper.eq("user_id",mallOrder.getUserId());
orderEntityQueryWrapper.orderByDesc("create_time");
List<OrderEntity> orderEntityList = orderService.list(orderEntityQueryWrapper);

//第二步 用lambda表达式取出商品ID的集合
if(orderEntityList!=null && orderEntityList.size()>0){
    List<Integer> proudctIdList = orderEntityList.stream().map(o -> o.getProductId()).distinct().collect(Collectors.toList());

//第三步 根据商品ID的集合查询商品列表
    QueryWrapper<ProductEntity> productQueryWrapper = new QueryWrapper<>();
    productQueryWrapper.in("product_id", proudctIdList);
    List<ProductEntity> productList = productService.list(productQueryWrapper);

//第四部将两个集合数据返回给前端
    respData.put("orderList",orderEntityList);
    respData.put("productList",productList);
}

这种实现方式,对于“双十一”商品标签这个需求,只需要数据库添加完字段,直接实体类扩展属性即可。所有使用到商品实体类做查询的接口自动就完成填充了。无需到dao层一个一个去修改select语句。

四、mybatis plus特性

总结:

虽然相比join多了一次数据库的交互,会稍微浪费点性能,但整体的代码可读性、可维护性和健壮性得到了很大提升。

 文章的最后,将网上摘来的mybatis plus特性的一张图呈现给大家了解:

Snip20201204_8.png

作者:谭文涛


发现文章有错误、对内容有疑问,都可以通过关注宜信技术学院微信公众号(CE_TECH),在后台留言给我们。我们每周会挑选出一位热心小伙伴,送上一份精美的小礼品。快来扫码关注我们吧!
分享硬核IT 专注金融