资讯详情

资讯详情

SpringBoot+Vue3+MyBatis美食网站系统开发实战

去年秋天接了个私房菜馆老板的单子对方想做一套在线点餐和菜品展示系统方便顾客在手机上翻菜单、提前下单、预约到店。我一听需求脑子里冒出的第一套方案就是 Java SpringBoot Vue3 MyBatis MySQL 的经典前后端分离组合。折腾了一个多月从空的 Maven 工程到能跑通浏览、搜索、下单、后台管理的完整 BS 网站系统中间踩了不少坑也沉淀了不少经验。这篇博文就把整个项目的技术选型思路、数据库设计、核心接口实现、认证鉴权、部署优化到排坑实录全部分享出来给正在做课设、毕设或者想接类似私活的朋友一份能直接参考的完整路线。这套系统的业务逻辑并不复杂但麻雀虽小五脏俱全。涉及用户端、商家端两个视角包含菜品浏览、分类检索、关键词搜索、菜品详情、购物车、订单提交、支付状态模拟、后台菜品管理、订单管理等模块。全文会按照一个真实项目从0到1的顺序来讲先交代需求怎么梳理、技术栈为什么这么选然后重点拆解数据库建模的细节和容易翻车的字段设计再逐块讲核心接口和前端页面的配合方式中间穿插 JWT Redis 登录鉴权的完整实现最后分享性能优化、部署上线以及开发过程中反复折腾我的几个坑。1. 项目从需求到成型为什么最后选了 SpringBoot Vue3 MyBatis1.1 需求不是想出来的是聊出来的接这个项目的第一件事不是打开 IDE而是跟老板聊了一下午。我发现绝大多数美食网站系统翻车的原因不在技术而在需求根本没聊透。比如老板一开始只说做个网站能看菜、能下单就行但追问下来才理清楚有几类角色、几条核心流程。这套系统最终定位为 BS 架构Browser/Server 架构即用户通过浏览器访问不需要安装客户端。核心角色有两个普通顾客和商家管理员。顾客能注册登录、浏览菜品、按分类筛选、搜索、加购物车、提交订单、查看历史订单管理员能维护菜品分类和菜品信息、上架下架、查看和管理订单状态。关键流程其实就两条一条是顾客浏览菜品 → 加购物车 → 提交订单 → 模拟支付 → 生成订单另一条是管理员登录后台 → 维护菜品 → 处理订单状态。把这俩流程在纸面上画清楚后续所有页面和接口的设计都变得非常有方向感。反过来如果一上来就闷头写代码很容易出现页面做完了但流程串不起来的情况。1.2 技术栈的合理性分析不追新只求稳SpringBoot 选它的理由不用多说约定优于配置内嵌 Tomcat一个 Jar 包就能跑生态极其成熟。对私活或者毕设这种体量的项目来说SSH 那套过于老旧Spring Boot 3.x 又太新部分依赖兼容性还没完全跟上所以我选了 Spring Boot 2.7.x稳定且资料多遇到问题一搜一大把。前端用 Vue3 是因为 Composition API 写起来逻辑聚合度更高配合 Vite 冷启动快开发体验明显优于 Vue2 的 Webpack 方案。再加上 Element Plus 组件库做后台管理界面几乎是拼积木几天的功夫就能把菜品表格、弹窗表单、状态标签这些页面全部搭出来。Vue3 的响应式机制在处理购物车增减、筛选条件联动这种交互场景时非常顺手。数据访问层选 MyBatis 而不是 MyBatis-Plus 或者 JPA纯粹是个人偏好。这个项目里有不少多表关联查询和多条件动态 SQLMyBatis 的 XML 文件能把复杂 SQL 写得很直白排查问题一目了然。虽然 MyBatis-Plus 能省掉大量单表 CRUD 的样板代码但考虑到某些 SQL 需要精细控制比如后文的推荐权重计算我还是选择了原生的 MyBatis 搭配一个轻量级的通用 Mapper 插件来兼顾效率。MySQL 没有什么悬念。成本低、部署简单、云服务器上装一个就能用对中小流量的网站系统完全够用。InnoDB 引擎支持事务和外键约束对这个项目里的订单一致性需求来说是必须的。开发环境 MySQL 8.0生产环境用了同样的版本避免出现版本差异导致的字符集或者排序规则问题。2. 数据库是第一场硬仗核心表设计与最容易被忽略的字段很多初学者拿到这种项目会先写实体类再建表这是本末倒置。我习惯先建模再写代码。美食网站系统的数据模型看起来简单但真要满足业务需求表的数量会远超你的想象。这套系统最终用了 9 张核心表这里挑最重要的几张展开。2.1 用户、菜品、分类三张基础表的设计要点用户表 user 是系统基础字段包含 id、username、password、nickname、avatar、phone、create_time 等。密码字段我用 BCrypt 加密后的字符串存储长度至少设 60否则后面加密串存不进去。头像字段只需要存相对路径或 URL不推荐直接把图片二进制丢进数据库后续图片存储策略在部署章节详细讲。菜品表 dish 是核心中的核心。名称、描述、图片、价格、分类 id、销量、评分、状态这些一个都不能少。有两个字段值得重点提醒。第一个是 status 状态字段我用 0 表示下架1 表示上架加了一个普通索引。所有前端查询菜品的地方都必须带上 status 1 的条件否则后台下架的菜还会出现在用户端这是我第一版测试时踩过的坑。第二个是冗余字段 category_name归类名字段很多新手不理解为什么菜品表里要冗余分类名直接在查询时 join 分类表不就行了原因很简单用户端菜品列表页高频使用减少一次 join 对性能有好处。菜品分类变化频率低冗余这点数据完全不会造成一致性问题。分类表 category 就简单了id、name、sort_order、status。sort_order 用于控制前端分类展示顺序status 也是软删除的标志位。真实业务中不建议物理删除分类否则历史订单关联的分类信息会变得不可追溯。2.2 购物车和订单表的表结构设计状态机思维购物车表 cart 字段比想象中简单id、user_id、dish_id、quantity、create_time、update_time。逻辑上不需要给购物车里的菜品存快照因为购物车是临时的用户结算前如果菜品下架或价格变动直接提示即可。订单表 orders 和订单明细表 order_item 是这套系统里最需要花心思的。orders 表包含 id、order_no、user_id、total_amount、status、pay_status、remark、create_time、pay_time 等核心字段。order_no 我采用时间戳加随机数的策略生成保证订单号唯一且不暴露业务量。金额字段必须用 DECIMAL(10, 2)绝不能用 float 或 double否则后续对账会出现 0.1 0.2 ! 0.3 的经典问题。订单状态我设计为整数字段:0 待支付、1 已支付待处理、2 已完成、3 已取消。支付状态单独用一个字段 pay_status避免把支付状态和订单状态混在一个字段里导致状态转换逻辑混乱。订单明细表 order_item 则保存下单那一刻的菜品快照包括 dish_name、dish_price、quantity、image_url这是系统设计里非常重要的一环。为什么不能只存 dish_id因为用户下单之后菜品可能改名、下架甚至删除而用户的订单历史必须显示他当时购买时看到的信息。凡是涉及历史单据的场景都必须做数据快照。2.3 建表 SQL 若干细节字符集、索引与自增主键建表时字符集统一用 utf8mb4不要用 utf8因为 utf8 在 MySQL 中无法存储 emoji 表情和部分生僻汉字。虽然系统里不一定有 emoji但移动端用户昵称、评价内容里出现特殊字符的概率很高直接用 utf8mb4 一劳永逸。排序规则用 utf8mb4_general_ci 足够追求更精确的排序可以用 utf8mb4_unicode_ci对中文搜索影响不大。索引设计遵循了最左前缀原则。菜品表的分类 id 和状态字段建了联合索引 idx_category_status(category_id, status)搜索场景用的关键词字段建了普通索引但模糊查询 LIKE %关键词% 其实走不了索引这一步在性能优化章节会专门讲方案。订单表的核心索引是 idx_user_id(user_id) 和 idx_create_time(create_time)分别支撑按用户查订单和按时间统计订单两个高频场景。主键统一用 BIGINT 自增这是最简单可靠的方案。有同事建议我用雪花 ID 或 UUID但对这个业务量来说自增 ID 完全没有瓶颈。真要到了分库分表的量级再迁移也来得及。系统里所有表的逻辑外键都不建物理外键约束只在代码层面维护关联关系。物理外键在电商系统里会严重拖慢插入和更新性能而且会导致删数据时各种约束报错增加运维成本。CREATE TABLE dish ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 菜品名称, description varchar(500) DEFAULT NULL COMMENT 菜品描述, image_url varchar(255) DEFAULT NULL COMMENT 图片地址, price decimal(10,2) NOT NULL COMMENT 价格, category_id bigint(20) NOT NULL COMMENT 分类ID, category_name varchar(50) DEFAULT NULL COMMENT 分类名冗余, sales int(11) DEFAULT 0 COMMENT 销量, rating decimal(2,1) DEFAULT 5.0 COMMENT 评分, status tinyint(4) DEFAULT 1 COMMENT 状态: 0下架 1上架, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_status (category_id,status), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品表;3. 前后端分离接口实战从菜品推荐到下单接口设计如何接地气3.1 统一返回体与 RESTful 风格约定的必要前后端分离开发最怕的不是功能难写而是接口各写各的。前端拿到数据不知道什么格式后端改个字段名前端要跟着改半天。开工第一天我就定下了统一返回体 Result所有接口无论成功失败都返回同一结构:code、msg、data。code 为 0 表示成功非 0 表示业务异常HTTP 状态码只用来表示请求本身是否成功。这么设计的核心原因是浏览器对 HTTP 状态码有各种默认行为比如 401 会弹登录框冗余的语义绕来绕去不如全部走业务状态码。RESTful 风格坚持了资源命名原则获取菜品列表 GET /api/dish/list获取详情 GET /api/dish/{id}添加购物车 POST /api/cart提交订单 POST /api/order/submit。动词藏在 HTTP Method 里路径上只出现名词。这种风格在团队协作中沟通成本最低前端工程师看路径就知道调的是什么资源。3.2 人气推荐算法不依赖推荐引擎SQL 也能做得很体面美食网站一般都有招牌推荐或者猜你喜欢。很多朋友一听推荐就觉得得上算法模型说实话小项目这么搞纯粹是过度设计。我的方案是设计一个推荐分数在 SQL 里直接排出来。分数公式为:推荐分 销量 × 0.6 评分 × 20 近30天新增订单数 × 0.2。销量代表大众口味评分代表品质新增订单数代表近期热度。三者综合排出来的结果既不呆板又能自然地把新品带出来。具体实现上我用了 MySQL 的日期函数和子查询。销量和评分直接查 dish 表近 30 天新增订单数则从 order_item 表按菜品聚合统计。虽然简单但实际运行下来的推荐效果非常理想热门菜品稳定在前排偶尔有些评分高但销量一般的潜力股也能冒出来不会永远是一成不变。SELECT d.id, d.name, d.price, d.image_url, (d.sales * 0.6 d.rating * 20 COALESCE(oi.last30_sales, 0) * 0.2) AS recommend_score FROM dish d LEFT JOIN ( SELECT dish_id, SUM(quantity) AS last30_sales FROM order_item oi JOIN orders o ON oi.order_id o.id WHERE o.pay_status 1 AND o.create_time DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY dish_id ) oi ON d.id oi.dish_id WHERE d.status 1 ORDER BY recommend_score DESC LIMIT 10;这种做法的好处是零额外存储成本不需要定时任务去维护推荐数据。虽然每刷新一次页面会多跑几次聚合查询但在菜品量不大的情况下性能完全没问题。真要到了菜品上万、并发上千的量级再上 Redis 缓存或专门的推荐服务也不迟。3.3 搜索功能的关键取舍MySQL 模糊查询能做到什么程度搜索这块我坦白说做得比较简单就是基于菜品名称的 LIKE 模糊查询。用户输入关键词匹配菜品名称中包含关键词的记录。中文分词、同义词、拼音搜索这些高级能力 MySQL 原生是不支持的硬做只会越挖越深。我做出的取舍是先用 LIKE 撑起基本搜索能力等业务确实需要更高级的搜索时再上 Elasticsearch 或其它搜索服务。不过 LIKE 查询有一个必须处理的细节:首尾通配符会让索引失效。WHERE name LIKE %鱼% 这种写法是全表扫描的。解决思路是加一个 fulltext 全文索引用 MATCH...AGAINST 做自然语言检索。MySQL 8.0 的全文索引对中文已经有一些基础的 ngram 分词支持对小规模的中文搜索场景效果尚可。唯一要注意的是全文索引和 LIKE 的结果集略有差异需要根据实际体验决定用哪种。-- 添加全文索引 ALTER TABLE dish ADD FULLTEXT INDEX ft_name (name) WITH PARSER ngram; -- 使用全文检索 SELECT * FROM dish WHERE MATCH(name) AGAINST(鱼 IN NATURAL LANGUAGE MODE) AND status 1;3.4 下单接口的事务边界与并发扣库存购物车选中菜品后提交订单这个接口涉及多张表的状态变更必须用事务保证原子性。我给它加上了 Transactional 注解事务内完成以下步骤:生成订单号、插入订单表、批量插入订单明细表、清空购物车对应菜品、菜品销量自增。并发控制想过几种方案。乐观锁方案是 update dish set sales sales 1 where id ?这样销量自增本身就是原子操作不存在丢失更新的问题。真正需要小心的反而是重复提交。用户快速点两次提交按钮前端按钮做了 loading 处理后端还必须用订单号唯一索引兜底。同一个用户同一秒提交两次相同的内容第二次插入会因为订单号冲突而失败异常被事务捕捉后回滚不会产生脏数据。还有一个容易翻车的细节:事务方法不能是 private同类内部调用也不会生效。如果事务方法被同一个类里的另一个方法直接调用Spring 的 AOP 代理不会拦截 this 调用事务就静默失效了。这个坑我后文排坑章节会单独详细展开。4. 登录鉴权不只是登录页JWT Redis 双保险的完整落地方案4.1 为什么选 JWT以及 Token 过期策略的设计用户登录功能表面看很简单前端收用户名密码后端比对数据库返回成功或失败。但真正要做得体面绕不开一个问题:登录之后怎么确认用户身份。Session 方案在前后端分离架构下显得很笨重跨域请求要把 SessionId 传来传去服务器集群还要处理 Session 共享。因此我选了 JWT(JSON Web Token)无状态方案。JWT 的原理可以理解成给用户发了一张加密身份证。服务端不保存这张证只负责验签。Token 包含三部分:Header(声明算法)、Payload(用户信息)、Signature(签名)。签名部分由服务端密钥对前两部分做 HMAC 计算生成只要密钥不泄露用户伪造的信息在验签阶段就会被识破。JWT 的天然缺点是无法主动失效。用户修改密码要求所有旧 Token 失效用户退出登录要求 Token 立即失效这些场景下光靠 JWT 自身机制解决不了。我的做法是引入 Redis 配合处理。登录成功后不仅返回 Token还把 Token 存入 Redis以用户 ID 为 KeyToken 为 Value并设置过期时间。每次请求校验 Token 合法性之后再查一下 Redis 里存的 Token 和当前请求携带的是否一致。不一致就说明这个 Token 已经被踢下线或者被新 Token 顶掉。这样的双保险实现起来很简单但安全性和体验都提升了一大截。4.2 后端拦截器的实现路径与白名单设计后端实现用了 Spring MVC 的拦截器(HandlerInterceptor)。拦截器在请求进入 Controller 之前执行这里做两道检查:第一解析请求头中的 Authorization 字段取出 JWT 字符串用密钥验证签名和过期时间第二用解析出的用户 ID 去 Redis 查 Token 并比对。两道检查全部通过才放行并把用户信息塞进 ThreadLocal 或 Request 属性方便后续业务代码获取当前登录用户。放行白名单是必须设计的。登录接口、注册接口、菜品列表、菜品详情、搜索接口这些无需登录就能访问的路径不能拦截。如果全部拦截前端首次加载页面就会因为 401 白屏。我用一个常量列表维护白名单路径支持前缀匹配比如 /api/dish/list、/api/dish/detail/、/api/user/login。这样既保证敏感业务的安全又不会误伤公开页面。4.3 Vue3 前端配合方案路由守卫与 Axios 拦截器双保险前端这套体系跟后端拦截器是配套的。Axios 请求拦截器统一在发起请求前从 localStorage 取出 Token 并加在请求头里。响应拦截器则统一处理业务错误码遇到 401 时清理本地登录态跳转到登录页并给出友好提示。Vue Router 的路由守卫负责前端页面级别的访问控制。为需要登录的路由配置 meta 字段记录 requiresAuth 为 true。每次路由跳转前全局前置守卫检查目标路由是否需要登录需要的话再看本地有没有 Token。有 Token 放行没有则重定向到登录页登录成功后再跳回原页面。这种方式能在用户还没发起任何接口请求前就拦截掉大部分未登录访问体验上比白屏再跳转好得多。JWT 密钥的生成和管理是一个常常被忽略的安全点。我用的密钥是一串 256bit 的随机字符串存放位置在 application.yml 里生产环境通过环境变量注入不写死在配置文件提交到代码仓库。HS256 算法要求密钥最少 32 字节长度不足会直接启动报错实测时我在这上面吃过一个莫名其妙的亏。5. 从能跑到扛得住缓存、静态资源与 Nginx 部署的关键改动5.1 热点菜品与推荐列表为什么要缓存项目开发完第一版后我做了个并发测试发现压力集中在菜品列表和推荐接口上。所有页面一加载就先查数据库数据库扛不住全在 SQL 慢查询日志里。优化的第一步是在菜品列表和推荐接口上加缓存。缓存实现选型我用了 Spring Cache 抽象层配合 Redis。在 Service 层方法上加 Cacheable 注解第一次查询后结果自动存 Redis后续相同参数的请求直接命中缓存数据库压力几乎归零。菜品的分类和推荐结果变化频率低缓存 30 分钟完全够用。后台管理员修改菜品信息时用 CacheEvict 注解清掉对应的缓存保证用户端最快速度看到最新菜品。缓存击穿和雪崩也顺带做了处理。推荐接口用 Cacheable(sync true) 开启同步模式防止缓存失效瞬间大量请求同时穿透到数据库。不同的业务缓存设置了不同的过期时间避免同一时间大面积过期导致数据库瞬间压力过大。5.2 商品图片处理策略本地存储、压缩与 CDN 相关思考图片处理是这个项目里最容易显得业余的地方。最初我直接让前端上传原图一张手机拍的照片动辄三四兆页面加载慢不说数据库和服务器磁盘的压力也很大。后来我调整了策略:前端在上传前先用 Canvas 压缩到合适的尺寸后端接收后再按宽高比例裁剪缩略图。具体做法是将图片保存在服务器的 /data/images 目录下数据库只存相对路径比如 /images/dish/20250315_xxxx.jpg。Spring Boot 配置了静态资源映射把 /images/** 映射到本地磁盘目录。Nginx 反向代理时再把所有图片请求直接交给 Nginx 处理Nginx 对静态文件的处理效率和缓存能力远高于应用服务器。图片有了稳定访问路径后后续为图片加 CDN 也非常方便只需要把 /images 目录同步到 OSS 或者 COS数据库路径切换成 CDN 域名即可。这套方案的扩展性比一发入魂的本地存储方案要灵活得多。5.3 Nginx 部署细节与前后端分离的正确姿势前后端分离项目部署有个经典误区:前端构建完的 dist 目录直接丢进 Nginx 根目录后端接口通过 /api 路径代理到应用服务器。这个思路是对的但有几个细节要处理好。第一个是前端路由的 history 模式刷新页面会出现 404。必须在 Nginx 配置里加上 try_files 指令把不存在的路径全部重写回 index.html。第二个细节是跨域问题。开发环境下 Vue 用 Vite 代理解决跨域生产环境部署在同一个域名下通过路径区分动态资源和静态资源天生不存在跨域问题。这比搞前后端分两个域名、再配置 CORS 的方案简单太多了。第三个细节是 Nginx 层启用 gzip 压缩对 JSON 接口响应和前端 JS 文件都有显著的体积压缩效果。server { listen 80; server_name your-domain.com; # 前端静态资源 root /var/www/food-web/dist; index index.html; location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 菜品图片静态资源 location /images/ { alias /data/images/; expires 30d; add_header Cache-Control public; } # 开启gzip gzip on; gzip_types text/plain text/css application/json application/javascript; }HTTPS 在这个项目里我是直接上了的。现在浏览器对非 HTTPS 页面的限制越来越多支付相关的功能在 http 环境下基本不可用申请免费证书后配置一下 Nginx 的 SSL 模块不算复杂但对项目的安全感和信任度提升很明显。6. 开发中被反复折腾的几个坑你要是也遇到能少走不少弯路6.1 时间字段的时区问题:前端显示永远差 8 小时这是我在测试用户下单时间时发现的问题。数据库里存的时间明明是 14:30前端展示出来变成了 06:30。根因是 JDBC 连接串没指定时区MySQL 驱动默认使用了服务器时区而 Jackson 序列化时又用了 UTC一来一回就偏了 8 小时。解决方案是在 JDBC URL 上显式追加 serverTimezoneAsia/Shanghai同时在后端统一配置 Jackson 的时间序列化格式规定所有日期时间字段输出为 yyyy-MM-dd HH:mm:ss。jdbc:mysql://localhost:3306/food_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai这个坑特别隐晦的地方在于本地开发时 MySQL 和 Java 应用在同一台机器上时区干扰可能不明显一旦云服务器上部署立马原形毕露。所以我建议从开发第一天起就统一时区配置省得后期排查调得怀疑人生。6.2 图片上传成功但页面不显示:SpringBoot 静态资源映射的两个版本差异项目前后端联调时遇到过图片死活加载不出来的情况。路径检查了很多遍数据库中存的地址也对浏览器直接访问也报 404。后来发现是 Spring Boot 的静态资源映射配置问题2.x 版本的资源映射器和老版本写法不太一样把本地目录映射到 URL 路径需要重写 addResourceHandlers 方法。这个问题在本地开发时因为前端 Vite 代理的存在常常被掩盖前端请求图片走了代理直接转发给后端后端处理没有报错图片路径在浏览器中也能预览。但打包部署到 Nginx 之后图片请求直接打到 NginxNginx 找不到对应资源才暴露出来。所以联调阶段必须时刻注意目前请求打到哪一层。6.3 事务失效:同类方法内部调用的经典陷阱事务失效的坑我前后遇到过三次最后一次才彻底搞明白原理。场景是提交订单时我又想给用户加积分于是把加积分的方法写在了同一个 Service 类里直接在 submitOrder 方法内部调用 addPoints 方法并且 addPoints 上也标了 Transactional。看起来两个方法都有事务注解但实际上 addPoints 的事务根本没生效。原因是 Spring 的 Transactional 是基于 AOP 代理实现的外部调用走代理对象事务拦截器能生效但同类内部 this.addPoints() 调用走的是原始对象不经过代理事务注解形同虚设。一旦 addPoints 内部抛出异常只会回滚它自己参与的那个事务如果它根本没有事务那就完全不回滚导致订单数据和积分数据不一致。解决办法有三个一是把加积分逻辑放到独立的 Service 类通过注入的方式跨类调用二是把两个方法放在同一个事务方法里让外层事务统一控制三是用 AopContext.currentProxy() 显式获取代理对象。第一和第二种方案业务上最清晰第三种方案虽然能解决但代码可读性较差。还有一个高频坑是异常被 catch 后事务不触发回滚。默认情况下只有 RuntimeException 和 Error 能回滚事务受检异常(Exception 的子类)不会触发回滚。如果业务代码里把 SQL 异常 catch 掉并返回了错误结果Spring 以为方法正常执行结束事务照样提交。最好的实践是事务方法内不 catch 异常让统一异常处理器去处理或者在注解上显式指定 rollbackFor Exception.class。6.4 MyBatis 字段映射的坑:驼峰和下划线的爱恨情仇数据库字段是下划线风格Java 实体类是驼峰风格MyBatis 如果没开启驼峰映射查出来的对象字段值全是 null。这个问题很经典但我还是要提因为太容易发生且隐蔽。在 application.yml 里加上一行配置即可但有一个例外需要特别注意:如果实体类里有数据库没有的字段比如推荐的评分、展示用的冗余字段必须要设置 resultMap 或者使用别名。写复杂 SQL 时我习惯把查询结果的列全部取别名alias 和实体类字段保持一致这样既规避了映射问题也方便排查 SQL 的执行逻辑。在这里真的想建议所有刚开始用 MyBatis 的朋友花半小时写一套整体的映射规范远比每个查询里写 resultMap 效率高。7. 写在最后的项目心得这种小系统最有价值的部分不在技术项目上线后我复盘了一下真正花时间的部分不是写 CRUD而是需求梳理、数据建模、联调排坑和部署策略这些看似没有技术含量的环节。很多朋友做类似系统时喜欢纠结要不要上微服务要不要用 Docker要不要用 Elasticsearch实际上对一个小而美的美食网站系统来说简单可靠才是第一原则。SpringBoot Vue3 MyBatis MySQL 这套组合足以支撑初期业务未来真有瓶瓶罐罐的规模再逐步演进也比一开始就搭一个重框架导致项目迟迟落不了地强得多。最后分享一个实战小技巧前后端联调时把接口文档的维护放在第一步哪怕只是一个简单的 Markdown 文件把每个接口的请求参数、返回结构、错误码列清楚后续的沟通成本能降低一半以上。这个项目从零到上线我在接口文档上花了大约半天时间之后整个联调期几乎没有因为接口格式问题返工过。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →