Day3
公共字段字段填充(AOP)
@Aspect
@Aspect 和 @Component 是 Spring AOP 中用于定义和管理切面的核心注解,二者结合使用才能使切面类生效。
@Aspect:标记类为切面
@Aspect
public class AutoFillAspect { ... }
-
标记当前类为切面类:告诉 Spring 该类包含 切入点(Pointcut) 和 通知(Advice) 的定义。
-
启用 AOP 相关功能:Spring 会识别该类中的
@Pointcut、@Before、@After等注解,并解析其中的 AOP 逻辑。 -
@Aspect本身不会将类注册为 Spring 容器的组件,必须配合@Component或其他组件注解使用。
@Component
@Component:将切面类纳入 Spring 管理**
@Component
public class AutoFillAspect { ... }
-
将类注册为 Spring 容器的组件:Spring 会自动扫描并实例化该类,使其成为 IoC 容器中的 Bean。
-
为 AOP 增强提供基础:只有当切面类是 Spring 管理的 Bean 时,Spring 才能对其进行 AOP 代理增强,使其能够拦截目标方法。
-
@Aspect定义切面逻辑:负责声明 “拦截哪些方法”(切入点)和 “在何时做什么”(通知)。 -
@Component注册组件:负责将切面类纳入 Spring 容器,使其能够被 Spring 的 AOP 机制识别和调用。
切入点
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
-
execution(\* com.sky.mapper.\*.\*(..)):匹配com.sky.mapper包下的所有方法。 -
@annotation(com.sky.annotation.AutoFill):匹配带有@AutoFill注解的方法。 -
组合条件:只有同时满足上述两个条件的方法才会被拦截。
通过这个组合可以与AutoFill注解进行关联
前置通知,在通知中进行公共字段的赋值
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){}
在 Spring AOP 中,JoinPoint 是一个核心接口,它提供了被拦截方法的上下文信息
JoinPoint 的核心作用
| 方法 | 作用 |
|---|---|
joinPoint.getSignature() |
获取方法签名(如方法名、参数类型)。 |
joinPoint.getArgs() |
获取方法的参数值(如实体对象 User user)。 |
joinPoint.getTarget() |
获取目标对象(如 UserServiceImpl 实例)。 |
joinPoint.getThis() |
获取代理对象(通常用于获取增强后的对象)。 |
获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); //获得方法上的注解对象
OperationType operationType = autoFill.value(); //获得数据库操作类型
通过反射机制从被拦截的方法中获取 @AutoFill 注解及其属性值,是实现自动填充逻辑的关键步骤
取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs(); //获取参数
if(args == null || args.length == 0){
return;
}
Object entity = args[0];
准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType == OperationType.INSERT){
//为4个公共字段赋值
try{
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, 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){
//为2个公共字段赋值
try{
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity, now);
setUpdateUser.invoke(entity, currentId);
}catch (Exception e){
e.printStackTrace();
}
}
在 Java 中,反射机制允许程序在运行时动态获取类的信息(如字段、方法)并调用它们。在 AOP 切面中,我们通过反射实现自动填充字段的核心步骤包括:获取方法参数、动态调用 setter 方法和注入值。
反射的核心 API
| 类 / 方法 | 作用 |
|---|---|
Class.getDeclaredMethod(String name, Class<?>... parameterTypes) |
获取类的指定方法(包括私有方法)。 |
Method.invoke(Object obj, Object... args) |
调用方法,传入对象实例和参数值。 |
Class.getDeclaredFields() |
获取类的所有字段(包括私有字段)。 |
Field.set(Object obj, Object value) |
设置字段值。 |
新增菜品
获取原始文件名并处理后缀
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
- 作用:提取文件后缀(如
.jpg、.png),用于生成新文件名,避免文件类型混乱。
生成唯一文件名
String objectName = UUID.randomUUID().toString() + extension;
UUID.randomUUID():生成全局唯一 ID(如550e8400-e29b-41d4-a716-446655440000),避免文件名冲突。- 拼接后缀:确保新文件名仍保留原始文件类型(如
550e8400.jpg)。
上传至 OSS 并获取访问路径
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
file.getBytes():将上传文件转换为字节数组。upload()方法:将字节数组上传至 OSS,返回文件的访问 URL(如https://bucket-name.oss-cn-hangzhou.aliyuncs.com/550e8400.jpg)。

常见问题
这里我们要用到阿里云服务器(不能直接用老师的,否则在前后端联调时会加载不出来图片)
首先去阿里云官网创建一个账号,学生可以免费申请3个月的对象存储OSS,然后创建一个Bucket
- endpoint:与地域有关,如果是北京就是$oss-cn-beijing.aliyuncs.com$,如果是杭州就是$oss-cn-hangzhou.aliyuncs.com$
- access-key-id与access-key-secret:需要自己申请AccessKey,在右上角头像那,申请后记得保存,后面不可以查看了
- bucket-name:就是自己的bucket名字
@Service
@Slf4j
public class DishServiceImpl implements DishService {}
@Service
@Service 是一个重要的注解,用于标记一个类为服务层组件。它是 @Component 的一个特定形式,主要用于标识业务逻辑层的类,使 Spring 能够自动扫描并将其注册为 Bean。
获取主键值
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
在 MyBatis 的 XML 映射文件中,<insert>标签的useGeneratedKeys="true"和keyProperty="id"是用于获取数据库自动生成的主键值的关键配置,尤其适用于自增主键(如 MySQL 的AUTO_INCREMENT
useGeneratedKeys="true"
- 作用:告诉 MyBatis,当前插入操作的主键值由数据库自动生成(如自增主键)。
- 原理:MyBatis 会通过 JDBC 的
getGeneratedKeys()方法从数据库获取自动生成的主键值。
keyProperty="id"
- 作用:指定将数据库生成的主键值赋值给 Java 实体类的哪个属性。
- 示例:
keyProperty="id"表示将生成的主键值赋给实体类的id属性(需实体类有对应的 setter 方法)。
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
通过Lambda表达式为集合中的每个口味对象关联菜品 ID
菜品的分页查询
菜品的分页查询与员工的分页查询基本相同,唯一不同的就是SQL语句的书写(多表查询)
select d.*, c.name as categoryName from dish d left outer join category c on d.category_id = c.id
通过左外连接进行多表查询,LEFT OUTER JOIN连接dish(菜品)表和category(分类)表,确保即使菜品没有分类也会被查询出来
<where>
<if test="name != null">
and d.name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
每个<if>标签通过test属性判断参数是否有效,若有效则拼接对应的 SQL 片段。
删除菜品
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) { //这里也可以用String
log.info("菜品批量删除:{}", ids);
dishService.deleteBatch(ids);
return Result.success();
}
@RequestParam
在 Spring MVC 中,@RequestParam 是一个用于从 HTTP 请求的查询参数(Query Parameters) 中获取参数值的注解。在这个代码中,它的作用是将前端传递的菜品 ID 列表参数绑定到方法的 ids 参数上。
删除菜品
//方法一:通过for循环---会产生多条SQL语句
for(Long id : ids) {
//删除菜品表中的菜品数据
dishMapper.deleteById(id);
//删除菜品关联的口味数据
dishFlavorMapper.deleteByDishId(id);
}
//方法二:通过集合删除---只会生成一条SQL语句
//sql :delete from dish where id in(?,?,?)
//根据菜品id集合批量删除菜品数据
dishMapper.deleteByIds(ids);
//根据菜品id集合批量删除关联的口味数据
//sql :delete from dish_flavor where id in(?,?,?)
dishFlavorMapper.deleteByDishIds(ids);
<delete id="deleteByIds">
delete from dish where id in
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
<foreach>是 MyBatis 动态 SQL 的核心标签,用于遍历集合或数组,动态拼接 SQL 片段。其属性含义如下:
| 属性 | 作用 |
|---|---|
collection |
指定要遍历的集合参数名。此处collection="ids"对应 Mapper 接口方法的参数List<Long> ids。 |
item |
遍历集合时,当前元素的别名。此处item="id"表示每次遍历的元素用id指代(如 1、2、3)。 |
open |
遍历开始时拼接的字符串。此处open="("表示生成(作为IN条件的开头。 |
close |
遍历结束时拼接的字符串。此处close=")"表示生成)作为IN条件的结尾。 |
separator |
元素之间的分隔符。此处separator=","表示用逗号分隔多个 ID(如1, 2, 3)。 |
修改菜品
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {}
@PutMapping
@PutMapping 是一个用于映射 HTTP PUT 请求 的注解,通常用于 更新资源 的操作。它是 @RequestMapping(method = RequestMethod.PUT) 的简化形式,在你提供的菜品管理代码中,它用于处理 “修改菜品” 的请求。
浙公网安备 33010602011771号