news 2025/12/30 6:58:43

苍穹外卖项目总结(一)[MyBatis-Plus,文件上传,Redis]

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
苍穹外卖项目总结(一)[MyBatis-Plus,文件上传,Redis]

苍穹外卖项目复习笔记

一、 MyBatis-Plus (MP) 核心应用

这部分是开发数据持久层的核心,重点在于理解MP如何简化开发以及如何处理复杂场景。

1. 基础 CRUD 与架构关系

  • BaseMapper vs ServiceImpl :
    • BaseMapper<T>: 位于DAO层。提供了最底层的数据库原子操作(insert, deleteById, update, selectList等)。直接操作数据库。
    • ServiceImpl<M, T>: 位于Service层。实现了IService<T>接口,内部默认注入了BaseMapper。它在BaseMapper的基础上进行了封装,提供了更高级的业务逻辑支持(如批量操作、链式调用)。
    • 总结: 简单SQL直接调Mapper,复杂业务逻辑或批量操作调Service。

2. 分页查询

  • 单表分页:
    • 配置: 必须配置MybatisPlusInterceptor并添加PaginationInnerInterceptor拦截器,否则分页无效(只会查全部)。
    • 实现: 使用Page<T>对象作为参数传入BaseMapper的查询方法中。
  • 多表关联分页查询:
    • 难点: MP的Wrapper主要针对单表。
    • 解决: 手写SQL(XML或注解)。
    • 关键: 只要Service方法定义的第一个参数是Page对象,MP拦截器会自动拦截该SQL,执行SELECT count(0)查询总数,然后注入LIMIT语句,无需手动写分页逻辑。

具体实现

自定义 SQL (XML 方式) —— 推荐

核心思路:编写一个 SQL,使用LEFT JOIN连接菜品表和分类表,直接查出你需要的所有字段,让 MP 帮你做分页。

  1. 定义 DishVO, 确保你的 DishVO 里有 categoryName 字段。
@DatapublicclassDishVOextendsDish{// 继承了 Dish 的所有属性// 额外添加分类名称privateStringcategoryName;// 可能还需要口味列表,根据你的业务需求决定是否在这里查,或者分开查// private List<DishFlavor> flavors;}
  1. 修改 Mapper 接口
    DishMapper.java中定义一个方法。 注意:
  • 入参必须包含 IPage,MP 会自动识别它并进行分页拦截。
  • 返回值也是 IPage。
@MapperpublicinterfaceDishMapperextendsBaseMapper<Dish>{/** * 自定义分页查询 DishVO * @param page MP 分页对象,必须作为第一个参数 * @param dishPageQueryDTO 查询条件 * @return 分页结果 */Page<DishVO>pageQuery(IPage<DishVO>page,@Param("dto")DishPageQueryDTOdishPageQueryDTO);}
  1. 编写 Mapper XML,在DishMapper.xml中编写 SQL。
<mapper namespace="com.sky.mapper.DishMapper"> <select id="pageQuery" resultType="com.sky.vo.DishVO"> SELECT d.*, c.name AS category_name FROM dish d LEFT JOIN category c ON d.category_id = c.id <where> <if test="dto.name != null and dto.name != ''"> AND d.name LIKE CONCAT('%', #{dto.name}, '%') </if> <if test="dto.categoryId != null"> AND d.category_id = #{dto.categoryId} </if> <if test="dto.status != null"> AND d.status = #{dto.status} </if> </where> ORDER BY d.create_time DESC </select> </mapper>
  1. Service 层调用
    直接调用 Mapper 即可,非常简洁。
@Override public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) { // 1. 准备分页对象 Page<DishVO> page = new Page<>(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize()); // 2. 调用 Mapper 自定义方法 // MP 会自动执行两条 SQL: // 第一条:SELECT COUNT(*) ... LEFT JOIN ... // 第二条:SELECT d.*, c.name ... LEFT JOIN ... LIMIT ?, ? Page<DishVO> voPage = dishMapper.pageQuery(page, dishPageQueryDTO); // 3. 封装结果 return new PageResult(voPage.getTotal(), voPage.getRecords()); }

3. 批量操作与链式调用 (Point 6)

  • 批量操作: 使用IService接口提供的方法,如saveBatch(List<T>)updateBatchById(List<T>)。底层是基于JDBC的batch操作,性能优于循环调用Mapper。
  • 链式调用 (LambdaQueryChainWrapper):
    • 场景: 避免创建繁琐的Wrapper对象。
    • 写法:lambdaQuery().eq(User::getId, id).one();
    • 优点: 代码更优雅,可读性更强。

4. 公共字段填充 (Point 4)

  • MP 原生方式 (MetaObjectHandler):
    • 原理: 实现MetaObjectHandler接口,重写insertFillupdateFill方法。
    • 配置: 在实体类字段上添加@TableField(fill = FieldFill.INSERT)等注解。
    • 场景: 适用于纯数据库层面的统一赋值(如create_time, update_time)。
为什么推荐 MetaObjectHandler?
  • 更底层: 它直接介入 MyBatis-Plus 的 CRUD 操作,是专门为数据填充设计的。

  • 更简单: 避免了复杂的 AOP 反射和切入点配置。

  • 更清晰: 逻辑集中在一个处理器类中,明确区分了插入更新两种场景。

实现步骤
步骤 1:创建元对象处理器

创建一个类继承自com.baomidou.mybatisplus.core.handlers.MetaObjectHandler,并实现insertFillupdateFill方法。

importcom.baomidou.mybatisplus.core.handlers.MetaObjectHandler;importorg.apache.ibatis.reflection.MetaObject;importorg.springframework.stereotype.Component;importjava.time.LocalDateTime;@ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{// 假设您有一个工具类来获取当前用户IDprivateLonggetCurrentUserId(){// 实际应用中:从 ThreadLocal 或 SecurityContext 获取当前登录用户IDreturn1L;// 示例值}/** * 插入操作时自动填充 */@OverridepublicvoidinsertFill(MetaObjectmetaObject){// 1. 填充创建时间if(metaObject.hasSetter("createTime")){this.strictInsertFill(metaObject,"createTime",LocalDateTime.class,LocalDateTime.now());}// 2. 填充创建人if(metaObject.hasSetter("createUser")){this.strictInsertFill(metaObject,"createUser",Long.class,getCurrentUserId());}// 3. 插入时通常也填充更新时间和更新人if(metaObject.hasSetter("updateTime")){this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}if(metaObject.hasSetter("updateUser")){this.strictInsertFill(metaObject,"updateUser",Long.class,getCurrentUserId());}}/** * 更新操作时自动填充 */@OverridepublicvoidupdateFill(MetaObjectmetaObject){// 1. 填充更新时间if(metaObject.hasSetter("updateTime")){this.strictUpdateFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}// 2. 填充更新人if(metaObject.hasSetter("updateUser")){this.strictUpdateFill(metaObject,"updateUser",Long.class,getCurrentUserId());}}}
步骤2:在实体类上添加注解

在您的实体类(如Employee)中,只需在需要自动填充的字段上添加@TableField注解,指定填充策略。

importcom.baomidou.mybatisplus.annotation.FieldFill;importcom.baomidou.mybatisplus.annotation.TableField;importjava.time.LocalDateTime;publicclassEmployee{// ... 其他字段@TableField(fill=FieldFill.INSERT)privateLocalDateTimecreateTime;@TableField(fill=FieldFill.INSERT)privateLongcreateUser;// 插入和更新时都需要填充@TableField(fill=FieldFill.INSERT_UPDATE)privateLocalDateTimeupdateTime;@TableField(fill=FieldFill.INSERT_UPDATE)privateLongupdateUser;}
  • AOP 方式:
    • 原理: 自定义注解 + Aspect切面。在切面中通过反射给参数对象的属性赋值。
    • 场景: 适用于需要获取上下文信息(如从ThreadLocal获取当前登录用户的ID)并填充到实体的场景。苍穹外卖中常结合两者使用。
苍穹外卖中AOP机制的分析

切面配置使用了前置通知和一个非常精确的 切入点表达式。
A. 切面类:AutoFillAspect

  • 在项目中,AutoFillAspect 是一个使用 @Aspect 注解的 Spring Bean。它很可能使用了 前置通知 (@Before) 来实现公共字段填充。
  • 执行逻辑: 在 Mapper 方法执行之前,通过反射获取方法参数(通常是实体对象),然后根据当前操作类型(INSERT 或 UPDATE)和当前登录的用户信息,为实体对象的 createTime、createUser、updateTime、updateUser 字段赋值。

B. 切入点表达式解析

切入点表达式是:
com.sky.mapper.*.*(..) && @annotation(com.sky.annotation.autoFill)\text{com.sky.mapper.*.*(..) \&\& @annotation(com.sky.annotation.autoFill)}com.sky.mapper.*.*(..) && @annotation(com.sky.annotation.autoFill)方法匹配部分:com.sky.mapper.*.*(..)com.sky.mapper.*: 匹配 com.sky.mapper 包下的所有类 (通常是 Mapper 接口)。.*(…): 匹配这些类中的所有方法 (方法名和参数数量不限)。
效果: 限制了切面只在 mapper 包下的方法上查找连接点。

注解匹配部分:@annotation(com.sky.annotation.autoFill)@annotation(...): 匹配所有被括号内指定的注解标注的方法。
效果: 只有当 com.sky.mapper 包下的某个方法同时被@AutoFill注解标记时,切面才会被织入。

总结: 只有当您在 Mapper 接口中定义的方法(如 insert 或 update 方法)上同时使用了@AutoFill注解时,AutoFillAspect 的逻辑才会在该方法执行时被触发。


二、 架构设计与难点解决

这部分涉及企业级开发的规范和常见“坑”的解决方案。

1. 雪花算法精度丢失问题

  • 现象: 后端ID为Long类型(19位),前端JS的Number类型最大安全整数是253−12^{53}-12531(约16位)。传递到前端时,后几位会被四舍五入,导致ID不一致。
  • 解决方案:后端将Long转为String传输
  • 实现方式:
    • 局部处理: 在实体类ID字段上加注解@JsonSerialize(using = ToStringSerializer.class)
    • 全局处理: 配置Jackson的消息转换器 (MappingJackson2HttpMessageConverter),将所有Long类型序列化为String。

2. 企业级文件上传设计

  • 技术栈: 阿里云 OSS (Object Storage Service)。
  • 设计模式:
    • XXXProperties: 使用@ConfigurationProperties读取application.yml中的配置(endpoint, key, secret, bucket),实现配置与代码分离。
    • XXXConfiguration: 使用@Configuration@Bean,利用Properties创建OSS客户端对象,交给Spring容器管理。
    • 接口设计: 定义标准的FileStorageService接口,包含upload方法。
    • 多实现类: 如果未来切换到腾讯云COS或MinIO,只需新增实现类并修改配置,无需修改业务代码(符合开闭原则)。

三、 中间件集成 (Redis)

StringRedisTemplate vs RedisTemplate

特性RedisTemplateStringRedisTemplate
序列化方式默认使用JdkSerializationRedisSerializer默认使用StringRedisSerializer
存储格式二进制流(乱码状,不可读)纯字符串(清晰可读)
占用空间较大(包含类信息)较小
适用场景存储复杂的Java对象,且不关心Redis中数据的可读性存储简单的KV结构,或者需要与其他语言交互,或者需要人工排查Redis数据
推荐一般不推荐直接用默认的。通常会自定义配置JSON序列化器。推荐使用。手动将对象转为JSON字符串存入,取出来再转回对象。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/22 17:11:58

heatmap.js v2.0终极迁移指南:快速升级你的热力图项目

heatmap.js v2.0终极迁移指南&#xff1a;快速升级你的热力图项目 【免费下载链接】heatmap.js &#x1f525; JavaScript Library for HTML5 canvas based heatmaps 项目地址: https://gitcode.com/gh_mirrors/he/heatmap.js heatmap.js作为业界领先的HTML5 canvas热力…

作者头像 李华
网站建设 2025/12/26 19:08:00

从英文困扰到建筑大师:我的Masa模组汉化蜕变之旅

从英文困扰到建筑大师&#xff1a;我的Masa模组汉化蜕变之旅 【免费下载链接】masa-mods-chinese 一个masa mods的汉化资源包 项目地址: https://gitcode.com/gh_mirrors/ma/masa-mods-chinese 还记得第一次打开Masa模组时&#xff0c;满屏的英文选项让我手足无措。直到…

作者头像 李华
网站建设 2025/12/27 4:51:28

Windows 11圆角禁用工具:一键回归经典直角窗口

Windows 11圆角禁用工具&#xff1a;一键回归经典直角窗口 【免费下载链接】Win11DisableRoundedCorners A simple utility that cold patches dwm (uDWM.dll) in order to disable window rounded corners in Windows 11 项目地址: https://gitcode.com/gh_mirrors/wi/Win11…

作者头像 李华
网站建设 2025/12/26 19:16:00

Calamari OCR终极指南:如何快速掌握高效文字识别技术

Calamari OCR是一款基于深度学习的开源光学字符识别引擎&#xff0c;专门为处理复杂文本布局和多种字体类型而设计。这个强大的工具结合了OCRopy和先进识别引擎的技术优势&#xff0c;通过TensorFlow框架实现高性能的文字检测与识别功能&#xff0c;让文档数字化变得简单高效。…

作者头像 李华