本课程以当前热门的外卖点餐为业务基础,业务真实、实用、广泛。基于流行的Spring Boot、mybatis plus等技术框架进行开发,带领学员体验真实项目开发流程、需求分析过程和代码实现过程。学完本课程能够收获:锻炼需求分析能力、编码能力、bug调试能力,增长开发经验。
问题分析:
前面我们已经完成了后台系统的员工管理功能开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字 段,在编辑员工时需要设置修改时间和修改人等字段。这些字段属于公共字段,也就是很多表中都有这些字段。能不能对于这些公共字段在某个地方统一处理,来简化开发呢?
答案就是使用Mybatis Plus提供的公共字段自动填充功能。
代码实现:
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以
统一对这些字段进行处理,避免了重复代码。
实现步骤:
1、在实体类的属性上加入@TableField注解,指定自动填充的策略
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口
在common包下新建MyMetaObjectHandler类,继承MetaObjectHandler接口,并实现insertFill和updateFill的两个方法
package com.raggie.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
}
@Override
public void updateFill(MetaObject metaObject) {
}
}
向EmployeeController中,涉及到公共信息的赋值添加注释
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
//设置初始密码为123456,进行md5加密处理
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
// employee.setCreateTime(LocalDateTime.now());
// employee.setUpdateTime(LocalDateTime.now());
// Long empID = (Long) request.getSession().getAttribute("employee");
// employee.setCreateUser(empID);
// employee.setUpdateUser(empID);
employeeService.save(employee);
log.info("新增员工,员工信息:{}",employee.toString());
return R.success("新增员工成功!");
}
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
log.info(employee.toString ());
// Long empID = (Long) request.getSession().getAttribute("employee");
// employee.setUpdateUser(empID);
// employee.setUpdateTime(LocalDateTime.now());
employeeService.updateById(employee);
log.info("员工信息修改为:{}",employee.toString());
return R.success("员工信息修改成功!");
}
在MyMetaObjectHandler设置自动填充属性赋值
package com.raggie.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;
/**
* 自定义元数据处理器
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@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 Long(1));
metaObject.setValue("updateUser",new Long(1));
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",new Long(1));
}
}
注意:当前我们设置createUser和updateUser:为固定值 后面我们需要进行改造,改为动态获得当前登录用户的id
功能完善:
前面我们已经完成了公共字段自动填充功能的代码开发,但是还有一个问题没有解决,就是我们在自动填充createUser和 updateUser时设置的用户id是固定值,
现在我们需要改造成动态获取当前登录用户的id。有的同学可能想到,用户登录成功后我们将用户id存入了HttpSession中,现在我从HttpSession中获取不就行了?
注意,我们在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以我们需要通过其他方式来获取登录用户id。 可以使用ThreadLocal来解决此问题,它是DK中提供的一个类。
实现步骤:
1、编写BaseContext.工具类,基于ThreadLocal封装的工具类
2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
在common包中新建工具类BaseContext,用于调用ThreadLocal保存用户id
package com.raggie.common;
/**
* 基于ThreadLocal封装工具类,用于保存id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId(){
return threadLocal.get();
}
}
回到MyMetaObjectHandler中,修改updateUser和createUser,调用BaseContext获取id值
package com.raggie.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;
/**
* 自定义元数据处理器
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@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",BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
}
需求分析:
后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时需
要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套
餐分类来展示对应的菜品和套餐。
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
package com.raggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 分类
*/
@Data
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//类型 1 菜品分类 2 套餐分类
private Integer type;
//分类名称
private String name;
//顺序
private Integer sort;
//创建时间
@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;
//是否删除
private Integer isDeleted;
}
package com.raggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.raggie.entity.Category;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}
package com.raggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.raggie.entity.Category;
public interface CategoryService extends IService<Category> {
}
package com.raggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.raggie.entity.Category;
import com.raggie.mapper.CategoryMapper;
import com.raggie.service.CategoryService;
import org.springframework.stereotype.Service;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}
package com.raggie.controller;
import com.raggie.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
}
在CategoryController创建save方法,用于保存菜品信息,添加@PostMapping注解。
调用categoryService的save方法保存信息,并调用R.success将操作成功信息返回给前端。
@PostMapping()
public R<String> save(@RequestBody Category category) {
categoryService.save(category);
log.info("菜品保存成功,信息为:{}",category.toString());
return R.success("菜品保存成功!");
}
仿照员工分页查询,在CategoryController中新建Page方法
@GetMapping("/page")
public R<Page> page(int page, int pageSize){
log.info("接收到查询请求,page={},pagesize={},name={}",page,pageSize);
//构造分页构造器
Page pageinfo=new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();
//添加排序条件
queryWrapper.orderByAsc(Category::getSort);
//执行查询
categoryService.page(pageinfo,queryWrapper);
return R.success(pageinfo);
}
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将参数(id)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service删除数据
3、Service调用Mapper操作数据库
在CategoryController中新建Delete方法,调用categoryService的removeById方法
@DeleteMapping()
public R<String> delete(Long id){
log.info("分类{}删除成功",id);
categoryService.removeById(id);
return R.success("分类删除成功!");
}
功能完善
前面我们已经实现了根据刷除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功
能完善。
要完善分类删除功能,需要先准备基础的类和接口:
1、实体类Dish和Setmeal(从课程资料中复制即可)
package com.raggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 套餐
*/
@Data
public class Setmeal implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//分类id
private Long categoryId;
//套餐名称
private String name;
//套餐价格
private BigDecimal price;
//状态 0:停用 1:启用
private Integer status;
//编码
private String code;
//描述信息
private String description;
//图片
private String image;
@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;
//是否删除
private Integer isDeleted;
}
package com.raggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
菜品
*/
@Data
public class Dish implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//菜品名称
private String name;
//菜品分类id
private Long categoryId;
//菜品价格
private BigDecimal price;
//商品码
private String code;
//图片
private String image;
//描述信息
private String description;
//0 停售 1 起售
private Integer status;
//顺序
private Integer sort;
@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;
//是否删除
private Integer isDeleted;
}
2、Mapperi接口DishMapper和SetmealMapper
package com.raggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.raggie.entity.Dish;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DishMapper extends BaseMapper<Dish> {
}
package com.raggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.raggie.entity.Setmeal;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SetmealMapper extends BaseMapper<Setmeal> {
}
3、Service:接▣DishService和SetmealService
package com.raggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.raggie.entity.Dish;
public interface DishService extends IService<Dish> {
}
package com.raggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.raggie.entity.Setmeal;
public interface SetmealService extends IService<Setmeal> {
}
4、Service:实现类DishServicelmpli和SetmealServicelmpl
package com.raggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.raggie.entity.Dish;
import com.raggie.mapper.DishMapper;
import com.raggie.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
}
package com.raggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.raggie.entity.Setmeal;
import com.raggie.mapper.SetmealMapper;
import com.raggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal>implements SetmealService {
}
在CategoryService创建remove方法
package com.raggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.raggie.entity.Category;
public interface CategoryService extends IService<Category> {
public void remove(Long id);
}
在CategoryServiceImpl调用DishService和SetmealService
private DishService dishService;
private SetmealService setmealService;
查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count = dishService.count(dishLambdaQueryWrapper);
if(count>0){
//已经关联了菜品
}
查询当前分类是否关联了套餐,如果已经关朕,抛出一个业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
count = setmealService.count(setmealLambdaQueryWrapper);
if(count>0){
//已经关联了套餐
}
若都没有关联,则调用removeById删除分类
super.removeById(id);
在common中创建自定义业务异常类CustomException,继承RuntimeException
package com.raggie.common;
public class CustomException extends RuntimeException{
public CustomException(String message){
super(message);
}
}
在remove方法中抛出异常
public void remove(Long id) {
//查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count = dishService.count(dishLambdaQueryWrapper);
if(count>0){
//已经关联了菜品
throw new CustomException("当前分类下关联了菜品");
}
//查询当前分类是否关联了套餐,如果已经关朕,抛出一个业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
if(count2>0){
//已经关联了套餐
throw new CustomException("当前分类下关联了套餐");
}
//
super.removeById(id);
}
在CustomException中创建异常处理方法
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
}
回到CategoryController中,在delete中调用刚刚修改的remove方法删除分类
public R<String> delete(Long id){
log.info("分类{}删除成功",id);
categoryService.remove(id);
return R.success("分类删除成功!");
}
需求分析
在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作
在中CategoryController创建update方法,添加@PutMapping注解,调用categoryService的updateById方法修改分类信息,并返回值
@PutMapping
public R<String> update(@RequestBody Category category){
log.info("修改分类信息:{}",category.toString());
categoryService.updateById(category);
R.success("修改分类信息成功!");
}