而针对于这些字段,我们的赋值方式为:
目前,在我们的项目中处理这些字段都是在每一个业务方法中进行赋值操作,如下:
如果都按照上述的操作方式来处理这些公共字段,需要在每一个业务方法中进行操作,编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?
答案是可以的,我们使用MybatisPlus提供的公共字段自动填充功能。
1.2.1思路分析
MybatisPlus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作,具体情况如下:
字段名
赋值时机
说明
createTime
插入(INSERT)
updateTime
插入(INSERT),更新(UPDATE)
createUser
updateUser
实现步骤:
1、在实体类的属性上加入@TableField注解,指定自动填充的策略。
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。
1.2.2代码实现
1).实体类的属性上加入@TableField注解,指定自动填充的策略。
在员工Employee实体类的公共字段属性上,加上注解,指定填充策略。(ps.在资料中提供的实体类,已经添加了该注解,并指定了填充策略)
FieldFill.INSERT:插入时填充该属性值FieldFill.INSERT_UPDATE:插入/更新时填充该属性值
2).按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。
所属包:com.itheima.reggie.common
编写完了元数据对象处理器之后,我们就可以将之前在新增和修改方法中手动赋值的代码删除或注释掉。
然后,我们启动项目,在员工管理模块中,测试增加/更新员工信息功能,然后通过debug或者直接查询数据库数据变更的形式,看看我们在新增/修改数据时,这些公共字段数据是否能够完成自动填充。
1.3.1思路分析
那么我先搞清楚一点,当我们在修改员工信息时,我们业务的执行流程是什么样子的,如下图:
1).LoginCheckFilter的doFilter方法
2).EmployeeController的update方法
3).MyMetaObjectHandler的updateFill方法
我们可以在上述类的方法中加入如下代码(获取当前线程ID,并输出):
经过上述的分析之后,发现我们可以使用JDK提供的一个类,来解决此问题,它是JDK中提供的ThreadLocal。
1.3.2ThreadLocal
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问当前线程对应的值。
ThreadLocal常用方法:
A.publicvoidset(Tvalue):设置当前线程的线程局部变量的值
B.publicTget():返回当前线程所对应的线程局部变量的值
C.publicvoidremove():删除当前线程所对应的线程局部变量的值
1.3.3操作步骤
1).编写BaseContext工具类,基于ThreadLocal封装的工具类
1.3.4代码实现
1).BaseContext工具类
1.3.5功能测试
后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。
在分类管理中,我们新增分类时,可以选择新增菜品分类(川菜、湘菜、粤菜...),也可以选择新增套餐分类(营养早餐、超值午餐...)。在添加套餐的时候,输入的排序字段,控制的是移动端套餐列表的展示顺序。
新增分类,其实就是将我们新增窗口录入的分类数据,插入到category表,具体表结构如下:
我们添加的套餐名称,是唯一的,不能够重复的,所以在设计表结构时,已经针对于name字段建立了唯一索引,如下:
在开发代码之前,需要梳理一下整个程序的执行过程:
1).在页面(backend/page/category/list.html)的新增分类表单中填写数据,点击"确定"发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
2).服务端Controller接收页面提交的数据并调用Service将数据进行保存
3).Service调用Mapper操作数据库,保存数据
可以看到新增菜品分类和新增套餐分类请求的服务端地址和提交的json数据结构相同,所以服务端只需要提供一个方法统一处理即可:
具体请求信息整理如下:
请求
请求方式
POST
请求路径
/category
请求参数
json格式-{"name":"川菜","type":"1","sort":2}
代码实现的具体步骤如下:
1).实体类Category
所属包:com.itheima.reggie.entity
所属包:com.itheima.reggie.mapper
所属包:com.itheima.reggie.service
所属包:com.itheima.reggie.service.impl
1).输入的分类名称不存在
2).输入已存在的分类名称
3).新增菜品分类
4).新增套餐分类
系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
1).页面发送ajax请求,将分页查询参数(page、pageSize)提交到服务端
2).服务端Controller接收页面提交的数据并调用Service查询数据
3).Service调用Mapper操作数据库,查询分页数据
4).Controller将查询到的分页数据响应给页面
5).页面接收到分页数据并通过ElementUI的Table组件展示到页面上
页面中使用的是ElementUI提供的分页组件进行分页条的展示:
我们通过浏览器,也可以抓取到分页查询的请求信息,如下:
具体的请求信息整理如下:
GET
/category/page
page=1&pageSize=10
在CategoryController中增加分页查询的方法,在方法中传递分页条件进行查询,并且需要对查询到的结果,安排设置的套餐顺序字段sort进行排序。
测试完毕后,大家会发现,我们查询数据库返回的类型为1或者2,但是实际展示到页面上的却是"菜品分类"或"套餐分类",这一块是在前端页面中进行处理的,处理代码如下:
在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。
在前端页面中,点击"删除"按钮,就会触发定义的方法,然后往服务端发送异步请求,并传递参数id,执行删除分类操作。
删除操作的具体执行流程如下:
1).点击删除,页面发送ajax请求,将参数(id)提交到服务端
2).服务端Controller接收页面提交的数据并调用Service删除数据
3).Service调用Mapper操作数据库
从上述的分析中,我们可以得到请求的信息如下:
DELETE
id=1395291114922618881
在CategoryController中增加根据ID删除的方法,在方法中接收页面传递参数id,然后执行删除操作。
4.5.1思路分析
在上述的测试中,我们看到分类数据是可以正常删除的。但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善。完善后的逻辑为:
那么在这里又涉及到我们后面要用到的两张表结构dish(菜品表)和setmeal(套餐表)。具体的表结构,我们目前先了解一下:
4.5.2准备工作
1).准备菜品(Dish)及套餐(Setmeal)实体类(课程资料中直接拷贝)
1).创建自定义异常
在业务逻辑操作过程中,如果遇到一些业务参数、操作异常的情况下,我们直接抛出此异常。
所在包:com.itheima.reggie.common
异常抛出之后,会被异常处理器捕获,我们只需要在异常处理器中捕获这一类的异常,然后给页面返回对应的提示信息即可。
4).在GlobalExceptionHandler中处理自定义异常
在全局异常处理器中增加方法,用于捕获我们自定义的异常CustomException
注释掉原有的代码,在delete方法中直接调用categoryService中我们自定义的remove方法。
功能完善的代码编写完毕之后,我们需要重新启动项目,进入管理系统访问分类管理,然后进行删除分类的测试,需要将所有情况都覆盖全,例如:
1).新增一个分类,然后再直接删除,检查是否可以正常删除成功。(新增的分类时没有关联菜品和套餐的)
2).在数据库表(dish/setmeal)中,找到一个与菜品或套餐关联的分类,然后在页面中执行删除操作,检查是否可以正常的提示出对应的错误信息。
在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作。
这里面大家会发现,修改功能我们还没有实现,但是当点击"修改"按钮的时候,我们并没有开发根据ID查询数据,进行页面回显的功能,但是页面的分类数据确实回显回来了。这是怎么做到的呢,我们来解析一下前端的代码实现(前端代码已经实现):
那么回显这一步的操作前端已经实现,我们就只需要开发一个方法,修改操作的方法即可。我们可以通过浏览器来抓取一下修改操作的请求信息,如图:
具体的请求信息,整理如下:
PUT
{id:"1399923597874081794",name:"超值午餐",sort:0}