我们每次创建和更新数据时,都会修改数据库中的特定字段。如果我们每次手动修改设定数据,就会增加大量的重复工作,因此我们可以使用MybatisPlus的强大作用,让其为我们自动填充相关字段。
MP公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以同时对这些字段进行处理,避免了重复代码。
具体步骤:
1,在实体类的属性上加入@TableFiled注解,指定自动填充的策略。如:
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
2,按框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口:
package com.wang.reggie.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 自定义元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
/**
* 插入操作,自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert]...");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",new value(1));
metaObject.setValue("updateUser",new value(1));
}
/**
* 更新操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
long id = Thread.currentThread().getId();
log.info("线程id为:{}",id);
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",new value(1));
}
}
前面我们已经完成了公共字段自动填充功能的代码开发,但是我们在自动填充createUser
和updateUser
时设置的是固定值,现在我们需要使其能够动态获取当前登录用户的id。这里可以使用ThreadLocal来解决此问题:
实现步骤:
1,编写BaseContext工具类,基于ThreadLocal封装的工具类。
package com.wang.reggie.common;
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
2,在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//前面的代码略过
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}
log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
3,在MyMetaObjectHandler的方法中调用BaseContext来获取登录用户的id
//修复前
metaObject.setValue("updateUser",new value(1));
//修复后
metaObject.setValue("updateUser",BaseContext.getCurrentId());
后台系统可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时就需要选择一个菜品分类和套餐分类,因此我们需要先完成菜品分类和套餐功能。页面如下:
ps:菜品分类和套餐分类用的同一张数据表category,通过type来区分(1:为菜品,2:为套餐)。
代码开发步骤:
1,在开发业务功能前,先将需要用到的类和接口基本结构创建好,详细代码不再赘述,具体编写可以参考用户employee的结构:
2,理清整个程序的执行过程。
页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
服务端controller接收页面提交的数据并调用Service将数据保存
Service调用Mapper操作数据库,保存数据
3,查看页面url请求,可以看到:
注:可以看到新增菜品和套餐分类请求的服务器地址和提交的json数据结构相同,所以服务端只需要提供一个方法统一处理就行。具体代码如下:
package com.wang.reggie.controller;
import com.wang.reggie.common.R;
import com.wang.reggie.entity.Category;
import com.wang.reggie.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping
public R<String> save(@RequestBody Category category){
log.info("category:{}",category );
categoryService.save(category);
return R.success("新增分类成功");
}
}
需要明白的两点是:
GlobalExceptionHandler
类中,进行异常抛出。系统中的分类很多的时候,一个页面中全部展示出来会显得比较乱,不宜与查看,所以一般的系统中都会以分页的方式来展示列表数据。页面需要的信息同员工分页查询相同:
程序执行流程:
1,页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端。
2,服务端Controller接收页面提交的数据并调用service层查询数据
3,Service调用Mapper操作数据库,查询分页数据
4,Controller将查询到的分页数据响应给页面
5,页面接收到分页数据并通过ElementUI的Table组件展示到页面上
整体代码实现,和员工信息分页查询一模一样:
/**
* 分页查询
* @param page
* @param pageSize
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
//分页构造器
Page<Category> pageInfo = new Page<>(page,pageSize);
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加排序条件,根据sort进行排序
queryWrapper.orderByAsc(Category::getSort);
//分页查询
categoryService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
在分类管理列表页面,可以对某个分类进行删除操作。但是需要主义的时当分类关联了菜品或者套餐时,此分类不允许删除。
1,当我们点击删除按钮时,页面发送ajax请求,将参数(id)提交到服务端。
2,服务端Controller接收页面提交的数据并调用Service删除数据。
3,Service调用Mapper操作数据库。
具体URL请求如下:
/**
* 根据id删除分类
* @param id
* @return
*/
@DeleteMapping
public R<String> delete(Long id){
log.info("删除分类,id为:{}",id);
categoryService.removeById(id);
return R.success("分类信息删除成功");
}
在4.2中,我们完成的是根据id删除分类的功能,但是在删除前没有检查删除的分类是否关联了菜品或者套餐,因此我们需要进行功能完善。
因为我们需要检查关联的菜品和套餐,因此我们需要导入Dish和Setmeal两个基础的类和接口:
1,实体类Dish和Setmeal、Mapper接口、Service接口和Service接口实现类。
2,从Mapper层开始分析,首先我们需要从Dish和Setmeal类中查找出是否有CategoryId=我们要删除分类的id的菜品和套餐,但是由于我们用了MP,因此我们不需要编写特定的SQL语句。
3,Service层需要调用Mapper层获取到查找结果,因此我们重写remove方法。在remove方法中,我们添加查询条件根据分类和套餐id进行查询:
如果结果大于0,说明该分类关联了菜品,则需要进行特定处理。
这里的特定处理,我们可以抛出一个我们自定义的业务异常。
CustomException类:
package com.wang.reggie.common;
/**
* 自定义业务异常类
*/
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
然后再通过全局异常处理器进行处理,在页面上显示出我们手动抛出的错误信息。
GlobalExceptionHandler类下新建的方法:
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
}
如果结果等于0,说明该分类未关联菜品,则直接调用父类的删除方法。
service接口:
package com.wang.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wang.reggie.entity.Category;
public interface CategoryService extends IService<Category> {
public void remove(Long ids);
}
Service接口实现类:
package com.wang.reggie.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wang.reggie.common.CustomException;
import com.wang.reggie.entity.Category;
import com.wang.reggie.entity.Dish;
import com.wang.reggie.entity.Employee;
import com.wang.reggie.entity.Setmeal;
import com.wang.reggie.mapper.CategoryMapper;
import com.wang.reggie.mapper.EmployeeMapper;
import com.wang.reggie.service.CategoryService;
import com.wang.reggie.service.DishService;
import com.wang.reggie.service.EmployeeService;
import com.wang.reggie.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;
@Override
public void remove(Long id) {
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count1 = dishService.count(dishLambdaQueryWrapper);
//查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
if(count1 > 0){
//已经关联菜品,抛出一个业务异常
throw new CustomException("当前分类下关联了菜品,不能删除");
}
//查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
if(count2 > 0){
//已经关联套餐,抛出一个业务异常
throw new CustomException("当前分类下关联了套餐,不能删除");
}
//正常删除分类
super.removeById(id);
}
}
以上是项目第三部分中的难点,我们在学习的时候一定要理清除执行逻辑,再搭建项目。
我们点击修改按钮时,跳转到编辑页面,在编辑页面中前端回显分类信息并进行修改,最后点击确定按钮完成修改操作。
1,点击编辑按钮时,页面跳转到修改页面并对数据进行回显,我们对数据进行修改。(这一步未与服务器交互)
2,当我们修改完成,点击确定按钮时,页面发送Controller请求,并将修改后的数据以json格式传回。
3,服务端controller接收页面提交的数据并调用Service将数据保存
4,Service调用Mapper操作数据库,保存数据。
具体代码如下:
/**
* 根据id修改分类信息
* @param category
* @return
*/
@PutMapping
public R<String> update(@RequestBody Category category){
log.info("修改分类信息:{}",category);
categoryService.updateById(category);
return R.success("修改分类信息成功");
}
扩展:ThreadLocal