公共字段自动填充
需求分析
本来是交给service的代码为entity来set公共的值,再交给数据库去处理,有公共的代码冗余,解决这个问题。
现在交给AOP切面,然后会拦截Mapper之中update或者insert的操作,来提前处理获取类似time和user的值,直接交给Mapper使用
AOP切面
方法:
自定义AutoFill,用于标识需要进行的公共字段自动填充方法
自定义切面类AutoFillAspect,统一拦截加入了AutoFill的方法,通过反射为公共字段赋值
在Mapper方法加入AutoFill注解
如果是查询操作就没有必要自动填充。
实现
自定义包com.sky.annotation和包下类AutoFill
package com.sky.annotation;
//自定义注解,用于标识某个方法要进行功能字段的自动填充处理
import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value(); //Mapper操作方法名称的实体类
}
自定义包com.sky.aspect和包下类AutoFillAspect
package com.sky.aspect;
//自定义切面,实现公共字段自动填充
import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Aspect
@Component
@Slf4j//酸辣粉四斤
public class AutoFillAspect {
// 切入点 注释是为了拦截Mapper的方法
@Pointcut("execution(* com.sky.mapper.*.*(..))&& @annotation(com.sky.annotation.AutoFill)" ) //在mapper类中的方法,然后还需要有annotation的定义
public void autoFillPointCut(){}
//前置通知 在通知中进行公共字段赋值
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException {
log.info("开始进行公共字段自动填充");
//获取当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获取的方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库的操作类型
//获取到当前被拦截方法的参数,获取实体对象比如Employee对象
Object[] args = joinPoint.getArgs();
if(args == null || args.length==0) {
return;
}
Object entity = args[0]; //因为对象不确定就用Object接受
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
if(operationType == OperationType.INSERT){
try{
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
//通过反射为对象赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
}catch (Exception e){
e.printStackTrace();
}
//为四个公共字段赋值
}else if(operationType == OperationType.UPDATE){
try{
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
}catch (Exception e){
e.printStackTrace();
}
}
//公共属性包括creatTime,createUser等
//再说一边User是通过Treadlocal来获得的
//根据操作类型,为对应的操作赋值
}
}
在Mapper类中EmployMapper的update和insert方法中加入注解
@AutoFill(value=OperationType.UPDATE)
流程
开始(Start):
流程从Mapper方法的调用开始,例如EmployeeMapper.insert或EmployeeMapper.update。
拦截Mapper方法调用(Mapper Method Call):
用户调用Mapper类中的方法,触发AOP拦截。
AOP拦截调用(AOP Intercepts Call):
AOP框架通过定义的切面AutoFillAspect拦截方法调用。
检查方法上是否有@AutoFill注解。
确定操作类型(INSERT或UPDATE)。
获取方法参数(Fetch Method Arguments):
从方法参数中提取实体对象,例如Employee对象。
检查操作类型(Check OperationType):
根据操作类型进行不同的处理。
如果是INSERT操作:
设置createTime、createUser、updateTime和updateUser字段。
如果是UPDATE操作:
只设置updateTime和updateUser字段。
调用方法设置字段(Invoke Methods to Set Fields):
使用反射机制调用实体类的方法来设置字段值。
继续执行原始方法(Proceed with Original Method Execution):
在完成字段赋值后,继续执行原始的Mapper方法,如将实体保存到数据库。
结束(End):
流程结束,返回结果。
新增菜品(需要获取前端信息,插入两个表一个Dish一个Dishfavor)
文件上传(主要是阿里OSS的配置,上传操作代码简单)
上传的文件到阿里oss之中,首先定义CommonController类来定义大家都有的操作,然后在application.yml和application-dev.yml之中配置好oss的信息,
server:
port: 8080
spring:
profiles:
active: dev
main:
allow-circular-references: true
datasource:
druid:
driver-class-name: ${sky.datasource.driver-class-name}
url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: ${sky.datasource.username}
password: ${sky.datasource.password}
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.sky.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
logging:
level:
com:
sky:
mapper: debug
service: info
controller: info
sky:
jwt:
# 设置jwt签名加密时使用的秘钥
admin-secret-key: itcast
# 设置jwt过期时间
admin-ttl: 7200000
# 设置前端传递过来的令牌名称
admin-token-name: token
alioss:
endpoint: ${sky.alioss.endpoint}
access-key-id: ${sky.alioss.access-key-id}
access-key-secret: ${sky.alioss.access-key-secret}
bucket-name: ${sky.alioss.bucket-name}
把Alioss的参数从AliOssUtil 给到我们的Controller
package com.sky.controller.admin;
import com.sky.constant.MessageConstant;
import com.sky.result.Result;
import com.sky.utils.AliOssUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.bcel.classfile.Constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@RestController
@RequestMapping("/admin/common")
@Api(tags="通用接口")
@Slf4j
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file){ //参数名和前端命名保持一致
log.info("文件上传:{}",file);
//将文件上传到阿里云上
try {
String originalFilename = file.getOriginalFilename();
//截取后缀
String extend = originalFilename.substring(originalFilename.lastIndexOf('.'));
//因为名字会有重复,所以我们使用UUID来防止重复的名字
String objectName = UUID.randomUUID().toString()+ extend;
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
return Result.error(MessageConstant.UPLOAD_FAILED);
}
//return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
获取到前端上传的文件,去post到oss服务器上,其中需要解决命名重复的问题,所以用uuid
菜品controller定义 (注释理解一下)
controller注释 通过注释统一交给springboot管理
@RestController // Spring MVC @RestController 是 @Controller 和 @ResponseBody 的组合注解,
@Slf4j //log
@Api(tags=“菜品相关接口”) //Swagger
@RequestMapping(“/admin/dish”) //Spring MVC
@RequestBody //Spring MVC 中的一个注解,用于将 HTTP 请求的正文(Body)转换为一个 Java 对象
package com.sky.service.impl;
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired //Spring 框架中的一个注解,用于自动装配 Spring 容器中的 bean(组件)
private DishMapper dishMapper;
@Transactional //用于声明性地管理事务。它可以应用于类或方法上,用于定义方法运行时的事务属性。在后台,Spring 使用 AOP(面向切面编程)来管理事务,这样开发者就无需手动管理事务边界,从而简化了代码。\
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO,dish);
}
}
controller写完之后写service
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired //Spring 框架中的一个注解,用于自动装配 Spring 容器中的 bean(组件)
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
@Transactional //用于声明性地管理事务。它可以应用于类或方法上,用于定义方法运行时的事务属性。在后台,Spring 使用 AOP(面向切面编程)来管理事务,这样开发者就无需手动管理事务边界,从而简化了代码。\
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO,dish);
dishMapper.insert(dish);
List<DishFlavor> flavors = dishDTO.getFlavors();
if(flavors != null && flavors.size()>0){
dishFlavorMapper.insertBatch();
}
}
service里面的所有关于数据库的操作,全部使用mapper来实现
具体操作:
在成员里面进行初始化Mapper
@Autowired //Spring 框架中的一个注解,用于自动装配 Spring 容器中的 bean(组件)
private DishMapper dishMapper;
然后在service里面调用成员mapper的方法
dishFlavorMapper.insertBatch();
再去具体的Mapper类里去实现,可以使用注释@实现,也可以使用Mapper.xml配置文件来实现
新增菜品(和员工类似都一样,重点写mapper.xml)
useGeneratedKeys=“true” keyProperty=“id” 可以执行完insert代码之后返回id的值
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name,category_id,price,image,description,create_time,update_time,create_user,update_user,status)
values
(#(name),#(categoryId),#(name),#(price),#(image),#(description),#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
</insert>
</mapper>
dishMapper.insert(dish);
Long id = dish.getId();
可以使用getid获得id
重点来了 useGeneratedKeys=“true” keyProperty=“id” 可以执行完insert代码之后返回id的值
还记得AOP操作吗
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);
在mapper的方法上面加上注释,就会自动填充公共字段了
新增口味 mapper里面
意思就是插入的是List的里面所选择的内容,区别于原来是#{name}这样的,这里是读取传入的List的内容再插入
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishFlavorMapper">
<insert id="insert">
insert into dish (name,category_id,price,image,description,create_time,update_time,create_user,update_user,status)
values
(#(name),#(categoryId),#(name),#(price),#(image),#(description),#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
</insert>
<insert id="insertBatch">
insert into dish_flavor (dish_id,name,value)
VALUES
<foreach collection="flavors" item="df">
(#(df.dishId),#(df.name),#(df.value))
</foreach>
</insert>
</mapper>
在 MyBatis 中, 元素用于在 SQL 语句中迭代集合(如 List、Set 或数组)并生成重复的 SQL 片段。这在需要批量插入、更新或删除操作时特别有用。
元素的使用
元素有几个常用属性:
collection:指定要迭代的集合(例如 List、Set 或数组)。
item:指定在迭代过程中表示集合中每个元素的变量名称。
菜品分页查询
流程 controller增加 page方法,返回pageResult,参数是dishpageQuertDTO
@GetMapping("/page")
@ApiOperation("分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
log.info("菜品分页查询,参数为{}",dishPageQueryDTO);
PageResult pageResult = dishService.pageQuert(dishPageQueryDTO);
return Result.success(pageResult);
}
Service中使用PageHelper传入getpage和pagesize,调用mapper函数,返回total个数和List of DishVO
public PageResult pageQuert(DishPageQueryDTO dishPageQueryDTO){
PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
long total = page.getTotal();
List<DishVO> result = page.getResult();
return new PageResult(total,result);
}
Mapper之中使用xml编写sql语句
现在有问题,搜索的数据有两个name,一个是dish的,一个是dishcategories的,起别名来解决
select d.* , c.name from dish d left outer join category c on d.category_id = c.id
```xml
<select id="pageQuery" resultType="com.sky.entity.Dish">
select d.* , c.name from dish d left outer join category c on d.category_id = c.id
<where>
<if test="name!=null">
and d.name like concat('%',#(name),'%')
</if>
<if test="categoryId != null">
and d.category_id like #{categoryId}
</if>
<if test="status!=null">
and d.status like #{status}
</if>
</where>
order by d.create_time desc
</select>
where那部分是动态查询,是为了让管理员可以查看不同口味或者不同名字的dish
删除菜品
Controller
@DeleteMapping
@ApiOperation("删除菜品")
public Result delete(@RequestParam List<Long> ids){
log.info("菜品批量删除:{}",ids);
dishService.deleteBatch(ids);
return Result.success();
}
使用?ids=的方式连接,所以需要RequestParam ,之前写成Body了
@RequestBody
用途:用于从 HTTP 请求体中提取数据。
绑定方式:将整个请求体转换为 Java 对象。
数据格式:适用于 JSON、XML 等复杂的数据格式。
使用示例
@RequestParam
用途:用于从 URL 查询参数或表单数据中提取参数。
绑定方式:绑定到 URL 的查询参数或表单参数。
数据格式:适用于简单的键值对数据。
@PathVariable
用途:用于从 URL 路径中提取参数。
绑定方式:绑定到 URL 路径中的变量。
数据格式:适用于路径中的变量。
service
@Transactional
public void deleteBatch(List<Long> ids){
//判断当前菜品是否能够删除--是否在售出
for(Long id:ids){
Dish dish = dishMapper.getById(id);
if(dish.getStatus() == StatusConstant.ENABLE){
throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
}
}
//是否被套餐关联了
List<Long> setmealIdsDishIds = setmealDishMapper.getSetmealIdsDishIds(ids);
if(setmealIdsDishIds!=null&& setmealIdsDishIds.size()>0){
throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
//删除菜品表中的菜品数据
for(Long id:ids){
dishMapper.deleteById(id);
//删除菜品关联的口味数据
dishFlavorMapper.deleteByDishId(id);
}
//DishMapper.delete(ids);
}
异常有MessageConstant常量来写明白,不需要自己写
Dish dish = dishMapper.getById(id);简单的直接注释@写sql语句就行了
Mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.SetmealDishMapper">
<select id="getSetmealIdsDishIds" resultType="java.lang.Long">
select setmeal_id from setmeal_dish where dish_id in
<foreach collection="dishId" separator="," open="(" close=")">
#{dishId}
</foreach>
</select>
</mapper>
@Select(“select setmea_id from setmeal_dish where dish_id in dishId”)
元素:
:这是 MyBatis 的动态 SQL 标签,用于处理集合类型的参数。它将集合中的每个元素插入到 SQL 语句中,生成一个适用于 IN 子句的列表。
collection=“dishId”:指定传入的集合参数的名称。
separator=“,”:指定集合元素之间的分隔符为逗号。
open=“(”:在生成的列表前插入一个左括号。
close=“)”:在生成的列表后插入一个右括号。
根据id回显数据
controller
@GetMapping("/{id}")
@ApiOperation("根据id查询菜品用于回显")
public Result<DishVO> getById(@PathVariable Long id){
log.info("查询菜品的id:{}",id);
DishVO dishVO = dishService.getByIdWithFlavor(id);
return Result.success(dishVO);
}
@RequestBody
用途:用于从 HTTP 请求体中提取数据。
绑定方式:将整个请求体转换为 Java 对象。
数据格式:适用于 JSON、XML 等复杂的数据格式。
使用示例
@RequestParam
用途:用于从 URL 查询参数或表单数据中提取参数。
绑定方式:绑定到 URL 的查询参数或表单参数。
数据格式:适用于简单的键值对数据。
@PathVariable
用途:用于从 URL 路径中提取参数。
绑定方式:绑定到 URL 路径中的变量。
数据格式:适用于路径中的变量。
service
public DishVO getByIdWithFlavor(Long id){
Dish dishbyId = dishMapper.getById(id);
List<DishFlavor> dishFlavors = dishFlavorMapper.getById(id);
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(dishbyId,dishVO);
dishVO.setFlavors(dishFlavors);
return dishVO;
}
Flavors是用List因为一个dish可以对应多个Flavor
修改菜品
分析
controller
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO){
log.info("修改菜品:{}",dishDTO);
dishService.updateWithFlavor(dishDTO);
return Result.success();
}
更新用PUT,需要所有的数据所以是@RequestBody
service
public void updateWithFlavor(DishDTO dishDTO){
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO,dish);
dishMapper.update(dish);
//先删除口味数据,再插入口味数据
dishFlavorMapper.deleteByDishId(dish.getId());
List<DishFlavor> flavors = dishDTO.getFlavors();
if(flavors != null && flavors.size()>0){
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dish.getId());
});
dishFlavorMapper.insertBatch(flavors);
}
}
Mapper
更改dish内容,使用动态sql
@AutoFill(value = OperationType.UPDATE)
void update(Dish dish);
自动填空公共字段
<update id="update" >
update dish
<set>
<if test="name != null">name = #{name},</if>
<if test="categoryId != null">category_id = #{categoryId},</if>
<if test="price != null">price = #{price},</if>
<if test="image != null">image = #{image},</if>
<if test="description != null">description = #{description},</if>
<if test="status != null">status = #{status},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="updateUser != null">update_user = #{updateUser},</if>
</set>
where id = #{id}
</update>
插入口味,先删除所有,再插入返回的所有List flavor
void insertBatch(List<DishFlavor> flavors);
<insert id="insertBatch">
insert into dish_flavor (dish_id,name,value)
VALUES
<foreach collection="flavors" item="df">
(#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
知识
Spring Boot 应用程序中常见的 Spring Beans 包括 @Service、@Repository、@Controller、@RestController、@Component、@Configuration、@Bean 以及 @Mapper 和 @ControllerAdvice 等