??博客x主頁(yè):己不由心王道長(zhǎng)??!
??文章說(shuō)明:SpringBoot項(xiàng)目-瑞吉外賣(mài)【day03】分類(lèi)管理??
?系列專(zhuān)欄:SpringBoot項(xiàng)目
??本篇內(nèi)容:對(duì)黑馬的瑞吉外賣(mài)項(xiàng)目的day03進(jìn)行筆記和項(xiàng)目實(shí)現(xiàn)??
??每日一語(yǔ):生活不可能像你想象得那么好,但也不會(huì)像你想象得那么糟。??
?? 交流社區(qū):己不由心王道長(zhǎng)(優(yōu)質(zhì)編程社區(qū))
前言
本次文章對(duì)應(yīng)所屬項(xiàng)目的第3天,我在想,我項(xiàng)目進(jìn)度到底是快了還是慢了。這個(gè)問(wèn)題有點(diǎn)深?yuàn)W,如果對(duì)于官方給的進(jìn)度,那我項(xiàng)目肯定是慢了;但是項(xiàng)目得消化,不能做完即可,圖個(gè)完成任務(wù)的心態(tài)是不可取的,所以還是慢慢來(lái)吧。
公共字段自動(dòng)填充
問(wèn)題分析
我們?cè)赿ay02已經(jīng)對(duì)后臺(tái)的員工管理功能進(jìn)行了開(kāi)發(fā),在新增員工時(shí)需要設(shè)置創(chuàng)建時(shí)間、創(chuàng)建人、修改時(shí)間、修改人等字段信息,在編輯員工時(shí)需要設(shè)置修改時(shí)間和修改人等字段信息。這些字段都是屬于公共字段,也就是很多表中都有的字段,如下所示:
基本每個(gè)表都有以上字段,而且我們?cè)诿恳粋€(gè)需要用到的修改、新增時(shí)都用到了這些公共字段。
這些代碼十分冗余,沒(méi)有技術(shù)含量,每次都寫(xiě)一遍是不可接受的。那么我們能不能對(duì)于這些公共字段做一個(gè)統(tǒng)一的處理,以便簡(jiǎn)化開(kāi)發(fā),讓代碼更加美觀呢?可以!
MybatisPlus為我們提供了公共字段自動(dòng)填充功能。
Mybatis Plus公共字段自動(dòng)填充,也就是在插入或者更新的時(shí)候?yàn)橹付ㄗ侄钨x予指定的值,使用它的好處就是可以統(tǒng)一對(duì)這些字段進(jìn)行處理,避免了重復(fù)代碼。
實(shí)現(xiàn)步驟:
1、在實(shí)體類(lèi)的屬性上加入@TableField注解,指定自動(dòng)填充的策略
可以看到,我們能在相應(yīng)的公共字段上,添加@TableField注解,然后在括號(hào)里選擇方式,最后選擇填充策略。填充策略有默認(rèn)、插入、插入和更新、更新四種。
@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;
以上是公共字段填充,為什么填充策略不同呢?這里解釋一下:
因?yàn)閏reateTime只有在新建的時(shí)候使用,而updateTime在插入的時(shí)候就已經(jīng)算更新了,在后面的更新中當(dāng)然也算。所以u(píng)pdateTime的策略是插入和更新時(shí)填充,同理可以理解其他幾個(gè)。
2.按照框架要求編寫(xiě)元數(shù)據(jù)對(duì)象處理器,在此類(lèi)中統(tǒng)一為公共字段賦值,此類(lèi)需要實(shí)現(xiàn)MetaObjectHandler接口
新建一個(gè)MyMetaObjectHandler:
代碼實(shí)現(xiàn)
package com.example.commons;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
/**
* 自定義公共字段自動(dòng)填充
* @author 不止于夢(mèng)想
* @date 2022/11/15 20:23
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* insert策略填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info(metaObject.toString());
log.info("insert填充策略......");
}
/**
* update策略填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info(metaObject.toString());
log.info("update填充策略......");
}
}
這里沒(méi)有進(jìn)行任何填充,先測(cè)試一下代碼是否能夠走通
功能測(cè)試
我們?cè)趗pdate策略里輸出日志并且打上斷點(diǎn),驗(yàn)證我們程序是否能夠執(zhí)行成功。
這是更新策略,所以我們修改員工信息:
可以看到,代碼是可以走通的。并且是在UPDATE之前,這就是我們想看到的
功能完善
這里其實(shí)把上面沒(méi)寫(xiě)的代碼一并在這里完成,這里原本是解決ThreadLocal問(wèn)題的,一并解決了吧。
先把update的里面這幾句注釋掉,現(xiàn)在要用公共字段填充,這些不寫(xiě)了,拜拜勒:
重啟項(xiàng)目發(fā)送更新請(qǐng)求:
注意看參數(shù),update時(shí)間跟我當(dāng)前時(shí)間不符合,說(shuō)明了現(xiàn)在沒(méi)有填充時(shí)間。下面依次完成需要的填充:
package com.example.commons;
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;
/**
* 自定義公共字段自動(dòng)填充
* @author 不止于夢(mèng)想
* @date 2022/11/15 20:23
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* insert策略填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
metaObject.setValue("createTime",LocalDateTime.now());
log.info("insert填充策略......");
}
/**
* update策略填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime", LocalDateTime.now());
log.info("update填充策略......");
}
}
上面這段代碼是不完整的,沒(méi)有設(shè)置本次插入或者更新的人的id,我們能不能用session對(duì)象設(shè)置呢?不行,因?yàn)樵诜椒▓?zhí)行的時(shí)候,真正的方法壓根沒(méi)有明著調(diào)用我們這個(gè)公共填充,而一次request請(qǐng)求你也給不了它。
解決辦法,首先我們要知道的是一次request請(qǐng)求其實(shí)對(duì)應(yīng)的是一次線程,而我們要用到的線程是JDK為我們提供的ThreadLocal.
這里我們先要確認(rèn)一件事情,就是每當(dāng)前臺(tái)發(fā)一次http請(qǐng)求,我們后臺(tái)對(duì)應(yīng)的服務(wù)器是不是分配了一個(gè)新的線程來(lái)處理:
多余解釋畫(huà)蛇添足,下面是官方給的方法,我們可以試試:
在處理過(guò)程中涉及到下面類(lèi)中的方法都屬于相同的一個(gè)線程:
1、LoginCheckFilter的doFilter方法
2、EmployeeController的update方法
3、MyMetaObjectHandler的updateFill方法
可以在上面的三個(gè)方法中分別加入下面代碼 (獲取當(dāng)前線程id):
long id Thread. current Thread() getId() :
Log. info(“線程id:1”,id) :
執(zhí)行編輯員工功能進(jìn)行驗(yàn)證,通過(guò)觀察控制臺(tái)輸出可以發(fā)現(xiàn),一次請(qǐng)求對(duì)應(yīng)的線程id是相同的:
可以知道的是一次請(qǐng)求確實(shí)是對(duì)應(yīng)一個(gè)線程,還得驗(yàn)證一件事情,就是不同請(qǐng)求不是一次線程。再發(fā)一次:
既然每次請(qǐng)求對(duì)應(yīng)一個(gè)線程,我們不可以共有一個(gè)請(qǐng)求,一個(gè)線程我們是可以共享的,而且別的請(qǐng)求線程也影響不到你的線程。
介紹ThreadLocal:
還是看一下官方解釋?zhuān)?br> 什么是ThreadLocal?
ThreadLocal并不是一個(gè)Thread,而是Thread的局部變量。當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。ThreadLocal為每個(gè)線程提供單獨(dú)一份存儲(chǔ)空間,具有線程隔離的效果,只有在線程內(nèi)才能獲取到對(duì)應(yīng)的值,線程外則不
能訪問(wèn)。
ThreadLocal常用方法:
public void set(T value):設(shè)置當(dāng)前線程的線程局部變量的值
public T get() :返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量的值
我們可以在LoginCheckFilter的doFilter方法中獲取當(dāng)前登錄用戶id,并調(diào)用ThreadLocal的set方法來(lái)設(shè)置當(dāng)前線程的線程局部變量的值(用戶id),然后在MyMetaobjectHandler的updateFil方法中調(diào)用ThreadLocal的get方法來(lái)獲得當(dāng)前線程所對(duì)應(yīng)的線程局部變量的值(用戶id)。
有了步驟咱就整它,打它啊,打它mad?。?/p>
實(shí)現(xiàn)步驟:
1、編寫(xiě)B(tài)aseContext工具類(lèi),基于ThreadLocal封裝的工具類(lèi)
package com.example.commons;
/**
* @author 不止于夢(mèng)想
* @date 2022/11/15 21:45
*/
public class BaseContext {
//設(shè)置成靜態(tài)屬性
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 設(shè)置線程局部變量
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 獲取線程局部變量的值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
2、在LoginCheckFilter的doFilter方法中調(diào)用BaseContext來(lái)設(shè)置當(dāng)前登錄用戶的id
if (httpServletRequest.getSession().getAttribute("employee")!=null) {
log.info("用戶已經(jīng)登錄"+httpServletRequest.getSession().getAttribute("employee"));
//獲取當(dāng)前請(qǐng)求的用戶id
long empId = (long) httpServletRequest.getSession().getAttribute("employee");
//設(shè)置當(dāng)前線程的線程局部變量的值
BaseContext.setCurrentId(empId);
filterChain.doFilter(httpServletRequest,httpServletResponse);
只有已經(jīng)登錄過(guò)的用戶才能獲取到對(duì)應(yīng)的id。
3、在MyMetaObjectHandler的方法中調(diào)用BaseContext獲取登錄用戶的id
metaObject.setValue("updateUser",BaseContext.getCurrentId());
測(cè)試:
最后把所有公共字段去掉
新增分類(lèi)
需求分析
在我們的分類(lèi)管理中,有兩個(gè)新增分類(lèi),分別是新增菜品分類(lèi)和新增套餐分類(lèi)。
新增菜品分類(lèi)和新增套餐分類(lèi)其實(shí)基本無(wú)差別,只是發(fā)給后臺(tái)時(shí)的type屬性不同。
在這個(gè)功能中,我們需要連接前端,并且在后端區(qū)分,然后把操作數(shù)據(jù)存入數(shù)據(jù)庫(kù)。
調(diào)用了axios、方法是post方法。
只判斷了code,所以新的controller應(yīng)該是String類(lèi)型。
模型
在這個(gè)功能中,我們的數(shù)據(jù)模型跟前面的不一樣了,不再是employee,而是category
兩個(gè)分類(lèi)模式,數(shù)據(jù)其實(shí)存入了一張表之中。
需要注意的是這里的name設(shè)置了唯一性約束,如果名字重復(fù)是會(huì)拋出異常的
代碼開(kāi)發(fā)
這里直接導(dǎo)入類(lèi)category:
package com.example.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;
/**
* 分類(lèi)
*/
@Data
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//類(lèi)型 1 菜品分類(lèi) 2 套餐分類(lèi)
private Integer type;
//分類(lèi)名稱(chēng)
private String name;
//順序
private Integer sort;
//創(chuàng)建時(shí)間
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//更新時(shí)間
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
//創(chuàng)建人
@TableField(fill = FieldFill.INSERT)
private Long createUser;
//修改人
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
}
接下來(lái)把service、mapper弄好。
然后·寫(xiě)好controller:
package com.example.controller;
import com.example.commons.R;
import com.example.entity.Category;
import com.example.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;
/**
* @author 不止于夢(mèng)想
* @date 2022/11/17 18:21
*/
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
CategoryService categoryService;
/**
* 新增分類(lèi)
* @param category
* @return
*/
@PostMapping
public R<String> save(@RequestBody Category category){
log.info("新增分類(lèi)");
boolean save = categoryService.save(category);
if(save) {
return R.success("新增分類(lèi)成功");
}
return R.error("新增分類(lèi)失敗");
}
}
功能測(cè)試
添加分類(lèi)成功,接口異常是接下來(lái)要處理的信息分頁(yè)查詢。
這里還有一個(gè)問(wèn)題就是,如果我們添加的菜品名字一樣,會(huì)出異常的,因?yàn)槲覀冊(cè)O(shè)計(jì)表時(shí)就已經(jīng)把name字段設(shè)置為非空了。我們測(cè)試一下
但是我們不會(huì)受到影響,因?yàn)槲覀冊(cè)谇懊嬉呀?jīng)設(shè)置了一個(gè)全局處理異常“Duplicate entry”
所以會(huì)提示我們已經(jīng)存在。
分類(lèi)信息分頁(yè)查詢
需求分析
在上面的新增分類(lèi),我們已經(jīng)提到了系統(tǒng)接口404異常,那么這個(gè)異常其實(shí)就是當(dāng)我們點(diǎn)擊分類(lèi)管理時(shí),頁(yè)面就會(huì)發(fā)送請(qǐng)求去后臺(tái)查詢數(shù)據(jù)并且返回展示了:
由上圖,當(dāng)我們點(diǎn)擊新增分類(lèi)時(shí),vue就創(chuàng)建了鉤子函數(shù),并調(diào)用了getCategoryPage方法。并且傳入了頁(yè)碼和頁(yè)碼所在頁(yè)的大小。其實(shí)就是一個(gè)分頁(yè)查詢,我們?cè)趀mployee時(shí)已經(jīng)做過(guò),所以這里直接跟進(jìn)getCategoryPage:
細(xì)節(jié)如圖。
代碼開(kāi)發(fā)
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
log.info("分頁(yè)查詢");
//構(gòu)造分頁(yè)構(gòu)造器
Page pageInfo = new Page(page,pageSize);
//構(gòu)造條件構(gòu)造器,輸出時(shí)要用到
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByAsc(Category::getSort);
//進(jìn)行分頁(yè)查詢
categoryService.page(pageInfo, queryWrapper);
return R.success(pageInfo);
}
功能測(cè)試
刪除分類(lèi)
需求分析
可以看到,我們的分類(lèi)管理后面其實(shí)是可以操作的,而這里要介紹的操作就是刪除分類(lèi)。
這里需要注意是當(dāng)分類(lèi)關(guān)聯(lián)了菜品或者套餐時(shí),此分類(lèi)是不允許刪除的。這里解釋一下,我們這里只是套餐分類(lèi),真正的細(xì)節(jié)并不是存在這個(gè)表里的,而是分別存在相應(yīng)的表中:
如上圖,分類(lèi)表只能表示有沒(méi)有當(dāng)前種類(lèi)和添加種類(lèi),刪除不歸它管理,如果不存在該種類(lèi),查詢時(shí)自然不顯示。
代碼開(kāi)發(fā)
我們還是先做簡(jiǎn)單的刪除
注意這里通過(guò)id刪除,但參數(shù)傳遞時(shí)是ids
@DeleteMapping
public R<String> delete(Long ids){
log.info("刪除操作......");
categoryService.removeById(ids);
return R.success("刪除成功");
}
功能完善
這里細(xì)節(jié)就不多說(shuō)了。
上代碼,不過(guò)那些需要導(dǎo)入和構(gòu)建架構(gòu)的代碼就不上了,太水:
CustomExcption:
package com.example.commons;
/**
* @author 不止于夢(mèng)想
* @date 2022/11/17 21:00
*/
/**
* 自定義異常
*/
public class CustomerExcption extends RuntimeException{
/**
* 傳入異常信息,交給父類(lèi)
* @param msg :異常信息
*/
public CustomerExcption(String msg){
super(msg);
}
}
CategoryServiceImpl:
package com.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.commons.CustomerExcption;
import com.example.entity.Category;
import com.example.entity.Dish;
import com.example.entity.Setmeal;
import com.example.mapper.CategoryMapper;
import com.example.service.CategoryService;
import com.example.service.DishService;
import com.example.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author 不止于夢(mèng)想
* @date 2022/11/17 18:19
*/
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
DishService dishService;
@Autowired
SetmealService setmealService;
/**
* 通過(guò)id刪除分類(lèi),刪除之前檢查有沒(méi)有關(guān)聯(lián)套餐(Setmeal)或者菜品(Dish),需要用到這兩者的服務(wù),所以在上邊進(jìn)行注入
* @param id
*/
@Override
public void remove(Long id) {
//判斷是否關(guān)聯(lián)Dish,設(shè)置查詢條件
LambdaQueryWrapper<Dish> dish = new LambdaQueryWrapper<>();
//菜品分類(lèi)id
// private Long categoryId;比較兩者id是否相等
//設(shè)置條件判斷,條件為傳入id與Dish表中的屬性CategoryId相等
dish.eq(Dish::getCategoryId,id);
//調(diào)用dishService服務(wù),查詢相等的條數(shù)
int count1 = dishService.count(dish);
//如果存在,則說(shuō)明關(guān)聯(lián),拋出異常,提示前臺(tái)
if(count1>0){
throw new CustomerExcption("菜品已被關(guān)聯(lián),不能刪除");
}
//判斷是否關(guān)聯(lián)Dish,設(shè)置查詢條件
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
//分類(lèi)id
//private Long categoryId;
//設(shè)置查詢條件
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
//調(diào)用setmealService服務(wù),查詢相等的條數(shù)
int count2 = setmealService.count(setmealLambdaQueryWrapper);
//如果存在,則說(shuō)明關(guān)聯(lián),拋出異常,提示前臺(tái)
if(count2>0){
throw new CustomerExcption("套餐已被關(guān)聯(lián),不能刪除");
}
//否則,則沒(méi)有關(guān)聯(lián),正常關(guān)聯(lián)分類(lèi),調(diào)用接口的ById方法
super.removeById(id);
}
}
別忘了再最開(kāi)始的地方更改為你剛修改的方法:
如果還是留著上次的方法,小心數(shù)據(jù)丟失(悲傷)。
測(cè)試:
接下來(lái)驗(yàn)證沒(méi)有關(guān)聯(lián)的能不能刪除,隆江豬腳飯是我剛添加的沒(méi)有關(guān)聯(lián):
修改分類(lèi)
需求分析
當(dāng)我們點(diǎn)擊修改時(shí),前端根據(jù)id進(jìn)行查詢,并進(jìn)行了一個(gè)回顯操作,這里就不細(xì)究了,我們可以看到這里可以更新兩個(gè)信息,名稱(chēng)和排序。
當(dāng)點(diǎn)擊確定時(shí),會(huì)把以上信息作為參數(shù)進(jìn)行查詢。
參數(shù)時(shí)id,name,和sort,但是更新時(shí)間什么的都會(huì)設(shè)置,所以這里直接用對(duì)象作為參數(shù)。返回值是code、請(qǐng)求時(shí)put,路徑明細(xì)如下:
代碼實(shí)現(xiàn)
@PutMapping
public R<String> update(@RequestBody Category category){
log.info("參數(shù):{}",category.toString());
categoryService.updateById(category);
return R.success("修改成功");
}
驗(yàn)證:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-835346.html
結(jié)尾
創(chuàng)作不易,喜歡的給個(gè)三連。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-835346.html
到了這里,關(guān)于【SpringBoot項(xiàng)目】SpringBoot項(xiàng)目-瑞吉外賣(mài)【day03】分類(lèi)管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!