核心知识点:

知识点1:公共字段自动填充

自定义注解本质上是一种特殊的接口,通过固定语法声明,核心包括:元注解(控制注解的行为) + 注解属性(存储注解的配置信息)


/**
 * 自定义注解,用于表示某个方法需要进行功能字段自动填充
 */

/**
 * 指定注解只能加在的位置
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
//用@interface声明这是一个注解
public @interface AutoFill {
    //指定数据库操作类型:UPDATE INSERT
    //因为这个公共字段只有在UPDATE INSERT才会使用
   
    OperationType value();//注解的属性:格式为[返回值类型 属性名();],这里的value是默认属性
}
元注解 你的配置 作用
@Target ElementType.METHOD 限定@AutoFill只能标注在方法上(不止)
@Retention RetentionPolicy.RUNTIME 注解保留到运行时(核心!只有运行时保留,后续切面才能通过反射读取注解信息)

3. 注解属性:存储业务配置

OperationType value(); 是注解的核心属性,作用是:
1.接受 update/insert 的枚举值,标记当前对应方法的数据库的操作类型
2.通过这个属性告诉切面 “该执行哪种填充逻辑:只有 INSERT/UPDATE 时需要填充公共字段
 
4.自定义注解@AutoFill的工作原理
注解本身是一个标记,没有任何执行逻辑,需要配合注解处理器AutoFillAspect才能实现“自动填充字段”的功能

 第一步:注解的 “标记” 作用

需要自动填充字段的方法上添加@AutoFill(OperationType.INSERT)@AutoFill(OperationType.UPDATE)
// 示例:给新增方法打标签,标记需要执行INSERT类型的字段填充
@AutoFill(OperationType.INSERT)
public void insertUser(User user) {
    // 原本只有插入逻辑,无字段填充
}

第二步:切面的 “解析 + 执行” 作用

AutoFillAspect是注解的 “处理器”,通过AOP(面向切面编程) 技术实现核心逻辑:
  • 第一步:通过@Pointcut定义 “切点”,匹配所有带有@AutoFill注解的方法;
  • 第二步:通过@Before/@Around等通知,在方法执行前拦截;
  • 第三步:反射读取方法上的@AutoFill注解,获取value(INSERT/UPDATE);
  • 第四步:根据操作类型,自动填充对应的公共字段(如 INSERT 填充createTime/createUser,UPDATE 填充updateTime/updateUser);
  • 第五步:执行原方法,完成数据库操作。

 

知识点2:新增菜品的开发

//开发逻辑:
//通过查看接口文档,了解接口和传参
//controller层
@PostMapping
@ApiOperation("新增菜品")
public Result<String> save(@RequestBody DishDTO dishDTO,DishFlavor dishFlavor){
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }
//接受参数,调用service层
//service层 (实现逻辑)
public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        //属性拷贝
        BeanUtils.copyProperties(dishDTO , dish);
        //分类状态默认为禁用状态0
        dish.setStatus(StatusConstant.DISABLE);
        dishMapper.insert(dish);

        //获取insert语句生成的主键值
        Long dishId = dish.getId();
//        插入口味
        List<DishFlavor> flavors = dishDTO.getFlavors();
        /**
         * 如何对口味进行处理:
         * 因为口味存储在一个列表里面,所以从列表中取出
         * 判断是否为空,size大于0(说明确实提交过来了)
         */
        if(flavors !=null && flavors.size()>0){
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            dishFlavorMapper.insertBatch(flavors);
        }
    }
//Mapper层实现
<insert id="insertBatch">
        insert into dish_flavor(name, dish_id, value)
        VALUES
        <foreach collection="flavors" item="df" separator=",">
            (#{df.name}, #{df.dishId}, #{df.value})
        </foreach>
    </insert>

 

属性 含义
collection 指定要遍历的集合参数名:必须和 Mapper 接口方法中传入的集合参数名一致(比如接口方法是insertBatch(List<DishFlavor> flavors),这里就写flavors;如果参数加了@Param注解,比如@Param("flavors") List<DishFlavor> flavors,也要对应写flavors);
item 遍历集合时,给当前元素起的别名:比如遍历flavors集合时,每拿到一个DishFlavor(口味对象),就用df代表它,后续可以通过df.属性名获取对象的属性值;
separator 指定每个循环生成的 SQL 片段之间的分隔符:批量插入时,VALUES后每个括号(())之间需要用逗号分隔,所以这里设为,,保证 SQL 语法正确;

知识点3:菜品分页查询的开发

没什么好说的,略

 /**
     * 菜品分页查询
     * @param dishPageQueryDTO
     * @return
     */
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        System.out.println("传递的name参数:" + dishPageQueryDTO.getName());
        //开始分页查询
        //使用Mybatias 提供的 pagehelper插件,简化分页的编写,一个参数想查的第几个分页,另一个是一页的size
        PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
        //pagehelper的返回值固定为page
        Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
        long total = page.getTotal();
        List<DishVO> records = page.getResult();
        return new PageResult(total,records);
    }
 <select id="pageQuery" resultType="com.sky.vo.DishVO">
        select d.* , c.name as categoryName 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 = #{categoryId}
            </if>
            <if test="status != null">
                and d.status = #{status}
            </if>

        </where>
        order by d.create_time desc
    </select>

知识点4:菜品删除的开发

/**
     * 菜品批量删除
     * @param ids
     * @return
     */
    @DeleteMapping()
    @ApiOperation("菜品批量删除")
    public Result<List<Dish>> deleteList(@RequestParam List<Long> ids){
        //本来传入的是string字符串,加上RequestParam注解,让MVC将字符串转成long型数组
        log.info("删除的菜品id,参数为:{}",ids);
        dishService.deleteBatch(ids);
        return Result.success();
    }
/**
     * 批量删除菜品
     *注意隐含的判断条件,删除两个地方(菜单和口味)
     * @param ids
     */
    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> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
        //如果关联了套餐就不能删除
        if(setmealIds != null &&setmealIds.size() > 0){
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }
        //删除口味数据
        for(Long id : ids){
            dishMapper.deleteById(id);
            //删除菜品关联的口味数据
            dishFlavorMapper.deleteFlavor(id);
        }
    }

知识点5:菜品修改的开发

 

posted on 2025-12-26 21:37  fafrkvit  阅读(0)  评论(0)    收藏  举报