一、 场景描述
假设有两张表:一张商品表、一张订单表,具体表的字段如下:
商品表和订单表是一对多的关系,一个商品可以有多个订单。现有如下需求:
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特性的一张图呈现给大家了解:
作者:谭文涛