首先是为什么需要缓存?
因为在用户端可能有成百上千个用户同时间从前端发出请求从而生成大量的SQL查询。导致用户端卡顿影响体验。

这里我们的缓存采用Redis,由于我们用户端是采用的是两个controller一个是DishController一个是setmealController用于查询菜品以及套餐的数据并且显示到前端。下图是为什么要分开做的原因:

以前的业务逻辑是直接去数据库查询,现在的逻辑如下图所示:

黑马教的是在controller中完成这个业务,这样的话实际上Redis存储的是Result对象,会有冗余等等原因如下图所示:

所以我们选择在service层实现缓存,这样Redis存储的就是纯净的数据,不包括什么msg。
现在的逻辑就是前端传来categoryId,调用service得到List<DishVO>再直接return 如下图所示:

重点在于service层的实现,首先由于查询DishVO实际上要查询两个表,一个是Dish一个是flavor,一个dish可能对应多个flavor,所以我们才会封装到DishVO中。
问题1:最开始我实现service的代码中是利用categoryId封装到Dish中去Dish表查询dishes,后利用for循环遍历dishes去查flavor表,这样的话效率就很低,就比如如果dishes有20个,我们完成一次操作就要发出20次SQL语句。
解决方案:在service调用mapper层利用left join一次查询出来并且封装进List<DishVO>中
service代码如下:

难点主要集中在dishMapper的listWithFlavor方法实现上,这个SQL比较难写,需要动态SQL。
原理就是利用封装的dish查询并且联立flavor表通过dish.id==flavor.dish_id,但是由于一个dish可能有多个flavor所以我们不能直接将查询结果封装进,而是通过XML映射。
首先是写我们的SQL如下图所示:

查询结果如下图所示:

由于一个dish有多个flavor所以会有堆叠同一个id有两行,因为它们的flavor不一样如下图所示:

显然51号dish的flavor有辣度和忌口两种,我们要把它们封装进DishVO,DishVO用List<Flavor>存储flavors,显然我们的SQL查询出来的不会这样封装,原因如下图所示:

接下来是resultMap如何写的问题
先展示出来再分析 如下图所示:

首先定义一个 id 后续select中要用到,表明它查出来的需要找到这个Map对应封装。其次type就是要封装成的对象也即是DishVO。
首先id字段用的是<id 表明这个如果后续碰到同样id的行就是同一个菜品,但是不同的flavor。
column表示的是数据库的字段名字,property表示的是上面DishVO的属性名需要一一对应
来到下面<collection 这里的property表示的是DishVO的属性名,但是在DishVO中是List<DishFlavor>也就是这个字段是一对多的,后面的ofType表示就是List存储的对象。
下面的column同样是数据库查询出的字段名,后面的property就对应了ofType中的属性名了,
这里由于是left join 只保留了一个Id 所以将查询出的Id同样给到了dishId
这样我们在下面的查询语句将以前的resultType=改成resultMap=就好了,这样查询的SQL就会自动帮我们封装进DishVO中!
之后返回controller层即可.....................添加菜品缓存结束.......................
2.由于需要确保数据一致性,即数据库的数据与缓存中的一致,所以我们需要清楚缓存中的数据,那么在什么时候需要清楚呢如下图所示:

(1)新增菜品的时候我们会选择一个category,如果没有即使更新缓存,客户访问的时候由于不走数据库所以会无法查询到新增加的菜品,所以在新增菜品这个操作后我们需要清楚对应category的缓存。

同样是放在service中进行原因如下图所示:

同理对于修改、状态修改、批量删除菜品这些,如果要清楚对应的缓存操作比较繁琐,所以这里我们选择清除所有菜品的缓存!

到这里菜品的缓存管理是结束了,轮到了套餐管理。
1.套餐管理有不同的点,它展示界面是分别用两个接口的

这个接口就是和菜品查询的一致,通过分类ID查询的,这里的查询不像菜品需要List<DishFlavor>因为菜品需要这个是因为前端需要这个展示如下图所示:

而套餐不需要,所以套餐直接返回的就是一个setmeal对象,查询出来的可以和setmeal一一对应上所以不需要resultMap。下图是service的代码:

下面是动态SQL代码:

这么的name可能是为了后续精确搜索做准备(复用)
2.除此以外用户还会查询套餐具体的内容如下图所示:

由于这些数据在上面那个接口文档中并没有给到前端,所以分开了两个接口,这个也需要缓存。
这个接口的点击套餐显示,点击套餐可以得到套餐id从而去查询这个套餐具体的内容!
service代码如下图:

封装到一个DishItemVO中

由于没有属性是list所以也不需要resultMap只需要对应封装就好了,但是这个由于一个套餐可能有多个Dish组成,所以我们返回的是List<DishItemVO>并且setmeal表中不含有copies以及name这些字段所以又要进行表的联立,SQL如下图所示:

接下来就是清理缓存
1.新增套餐不会影响套餐详细,只会影响套餐列表所以会清除套餐列表对应的缓存如图所示:

2.修改套餐内容的话,此时套餐列表变了,套餐详细也变了,最稳妥的是清理这俩所有缓存!

3.起售停售套餐也一样的同2

- 批量删除套餐同样要全删
