首页 / 资讯中心 / 文章详情

Spring Boot美食评价系统:从数据库设计到部署全解析

Spring Boot美食评价系统:从数据库设计到部署全解析 ★ FEATURED ARTICLE
做了两年多的Java后端大大小小的管理系统写过不少但真正让我把一个项目从零开始完整梳理、把源码整理到可以直接交付给别人跑起来的还是最近这套Spring Boot美食评价管理系统。这个项目本身不算复杂但它覆盖了一个典型业务系统从建模、开发到交付的所有关键环节源码包编号82480拿到手就能跑这一点在交付的时候帮了我大忙。这篇博文就把这个项目的核心设计思路、开发过程中踩过的坑、以及源码里几个值得留意的细节一次性说清楚希望能给正在做Spring Boot毕设、课程设计或者想练手完整项目的朋友一些参考。1. 为什么做美食评价系统需求拆解与业务边界先说清楚这个项目解决的到底是什么问题。市面上已经有美团、大众点评这类成熟的评价平台功能强大、维度复杂但对于一个学习型项目或者中小型私域场景来说那些系统的业务模型太冗余了——什么会员等级、消费返券、商家入驻审核、UGC内容审核一上来就是几十张表根本不适合用来理解一个评价系统最核心的骨架是什么。1.1 轻量级美食评价的核心业务闭环我做的这个系统业务边界非常清晰就围绕三个角色和一条主链路展开。三个角色分别是游客、注册用户、管理员。游客可以浏览美食信息和查看评价注册用户除了浏览还能发布评价、修改自己的评价、收藏喜欢的美食管理员则负责维护美食信息、管理用户状态、查看系统统计数据。一条主链路就是用户登录 → 浏览美食列表 → 查看美食详情 → 发表评价打分文字 → 系统更新综合评分 → 榜单排序变化。这个闭环几乎涵盖了所有评价类系统的通用逻辑而且每一步都有清晰的CRUD操作和业务规则特别适合用来展示Spring Boot的开发流程。相比之下如果一上来就搞商家端、骑手端、运营后台项目规模翻了三四倍核心的评价逻辑反而被稀释了。1.2 源码交付时的模块划分源码包82480里我按标准的Maven多模块思路拆分了工程但考虑到很多朋友拿到的Spring Boot项目都是单模块结构这个项目最后采用了单模块清晰分包的方式比多模块更容易上手。核心分包如下config全局配置类包括跨域配置、拦截器注册、静态资源映射controller接口层接收前端请求、参数校验、返回统一响应service业务逻辑层事务控制、评分计算、业务规则校验都在这层mapperDAO层数据访问层本项目用的是Spring Data JPAentity实体类与数据库表结构一一对应dto数据传输对象避免直接暴露实体给前端防止循环引用和多余字段泄露common统一返回结果封装、异常处理、常量定义这种结构的优势在于每一层的职责非常单一排错的时候能快速定位。比如评价发布后评分没更新那就先看service层的计算逻辑再看事务边界是否正确而不会在controller里堆业务代码。2. 技术选型与工程搭建Spring Boot版本到底怎么选技术选型是很多项目一开始就卡住的地方尤其是Spring Boot的版本问题。这方面我确实踩过教训得详细说说。2.1 Spring Boot版本选择的经验教训不是越高越好项目初始阶段我直接选了当时最新的Spring Boot 3.x版本。结果一开工就遇到一连串问题javax.servlet包名改成了jakarta.servletSpringfox Swagger 2.x完全不兼容MyBatis Plus的某些旧版本分页插件直接起飞连连接池的配置项都变了。这些问题的本质原因是Spring Boot 3.x是一次重大版本更新底层从Java EE规范转向了Jakarta EE 9规范大量第三方库必须同步升级到适配版本否则就会出现类找不到、方法签名对不上、注解不生效等问题。如果你是学习项目、毕业设计或者需要快速交付源码给别人跑起来我强烈建议选择Spring Boot 2.7.x系列。原因很简单生态兼容性最好。市面上能找到的教程、第三方文档、CSDN博客绝大多数是基于2.x版本写的。稳定且成熟。2.7.x是Spring Boot 2.x的最后一个版本官方维护周期长bug修复充分。Java版本要求适中。2.7.x支持Java 8到Java 17很多人的本机环境就是JDK 8而Spring Boot 3.x强制要求JDK 17及以上光这一点就够折腾半天了。提示如果你非要上3.x请确认三件事——JDK环境是17、所有第三方依赖都已适配Jakarta规范、不再使用Springfox Swagger而是用springdoc-openapi。否则你可能会花一个下午去解决为什么注解不生效这种跟业务无关的问题。2.2 快速搭建工程骨架的细节我用了Spring Initializr来生成基础工程这里有几个细节值得注意第一打包方式选jar而不是war。现在的部署方式基本不依赖外部Tomcat了Spring Boot内嵌Tomcat直接跑jar包最省事。第二依赖别一次加太多。我见过很多人生成项目时勾了一堆依赖什么Actuator、Web Services、JMS最后根本用不上还拖慢启动速度。这个项目只需要Spring Web、Spring Data JPA、MySQL Driver、Lombok、Validation。安全认证方面也没用Spring Security——因为项目体量小用拦截器加自定义注解实现简单的登录校验完全够用而且更容易让初学者理解登录态控制的原理。第三Lombok的引入虽然方便但也埋了一个小坑。eclipse编译器在处理Lombok注解时偶尔会抽风导致getter/setter找不到。所以我在源码包里的pom.xml中把Lombok的版本显式固定了避免因为IDE的annotation processing配置问题导致编译失败。2.3 工程里的几个招牌配置application.yml是整个项目的核心配置我写了详细的注释这里挑几个关键的说说server: port: 8080 servlet: context-path: /food spring: datasource: url: jdbc:mysql://localhost:3306/food_rating?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true open-in-view: false这里有个很重要的坑spring.jpa.open-in-view默认是true。它会开启OSIV模式也就是在Controller层也保持EntityManager打开这会导致事务边界失控、数据库连接长时间占用、在高并发下直接把连接池打满。我在开发过程中遇到过连接池耗尽导致接口全部超时的问题就是这个设置引起的。所以生产环境一定要设置为false。ddl-auto: update适合开发阶段表结构变了自动更新但生产环境建议改成validate或者none通过Flyway管理表结构。我在项目中用了一个技巧开发期用update部署前手动执行一份schema.sql避免update误改数据表结构。3. 数据库建模评价系统的核心是评分体系设计数据库设计直接决定了一个系统的上限。美食评价系统表面看是增删改查但真正的核心在评分数据上——怎么存、怎么更新、怎么统计每一步都影响系统的性能和准确性。3.1 四张核心表的设计思路我用四张表覆盖整个业务user用户表、food美食信息表、evaluation评价表、category美食分类表。先看用户表CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), avatar VARCHAR(255), role TINYINT NOT NULL DEFAULT 1 COMMENT 1-普通用户 2-管理员, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-正常 0-封禁, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;密码字段我设置了100个字符的长度因为存储的是BCrypt加密后的哈希串不是明文密码。这一点很多初学项目都踩坑——密码字段设置成varchar(20)加密后直接数据库报错。美食信息表我设计了几个关键字段name名称、image封面图URL、category_id分类ID、price人均价格、address地址、description描述、avg_score综合评分、evaluation_count评价数量。注意这个avg_score字段它不是用户直接传入的而是系统根据所有评价动态计算后写入的冗余字段。为什么不用实时聚合查询因为首页的美食列表需要按评分排序如果每次查询都去evaluation表做AVG()聚合随着数据量增长数据库的压力会指数级上升。用冗余字段存储计算结果通过事务保证一致性是典型的空间换时间思路。3.2 评价表设计为什么拆成总分和分维度评价表的设计我纠结了一段时间最终采用了总分多个维度分的组合方式。表结构如下CREATE TABLE evaluation ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, food_id BIGINT NOT NULL, taste_score TINYINT NOT NULL COMMENT 口味评分1-5, environment_score TINYINT NOT NULL COMMENT 环境评分1-5, service_score TINYINT NOT NULL COMMENT 服务评分1-5, content VARCHAR(1000) COMMENT 评价内容, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_food_id (food_id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;综合评分由三个维度分按权重计算得出权重比是口味5环境3服务2。这个权重不是随便定的而是参考了餐饮行业用户评价的核心关注点——口味永远是第一位的环境和服务次之。可能你会有疑问为什么不在表里直接存一个总分的冗余字段呢原因是这样做的维护成本太高。用户只能修改评价内容三个维度分如果变了总分就要同步更新一旦在应用层漏了一处就会造成数据不一致。与其存一个可能出错的总分不如存三个原始维度分在service层计算总分再写入food.avg_score里。评分更新的完整逻辑每次新增或修改评价service层执行一个原子操作根据food_id查询该美食的所有评价记录累加所有评价的加权总分除以评价总数得到新的平均分更新food表的avg_score和evaluation_count字段如果你觉得每次都全量聚合太慢可以考虑用增量更新的方式记录修改前后的分数差异一次UPDATE完成。但考虑到这个项目的数据量级单店评价几千条封顶全量聚合的准确性和可维护性更好性能损耗可以忽略不计。4. 核心业务逻辑实现登录拦截、评价发布与榜单统计业务逻辑是系统的心脏这里我挑三个最有代表性的模块详细展开。4.1 登录拦截器的实现原理解析我没有引入Spring Security用的是HandlerInterceptor加自定义注解的方案。自定义了一个RequireLogin注解凡是标注了这个注解的Controller方法在进入业务逻辑之前都会先经过登录校验。拦截器的核心逻辑如下public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod (HandlerMethod) handler; RequireLogin requireLogin handlerMethod.getMethodAnnotation(RequireLogin.class); // 从Redis或Session中取登录态 String token request.getHeader(Authorization); if (token null || token.isEmpty()) { throw new BizException(401, 未登录请先登录); } User user userService.getUserByToken(token); if (user null || user.getStatus() ! 1) { throw new BizException(401, 登录状态失效或账号已被禁用); } // 将用户信息放入ThreadLocal供后续业务使用 UserContext.set(user); } return true; } }为什么不用Spring Security因为这个项目的权限模型极其简单——只有登录/未登录和管理员/普通用户两种维度。引入Spring Security意味着要配置SecurityFilterChain、UserDetailsService、密码编码器、异常处理等一系列东西对于学习型项目来说反而造成了认知负担。拦截器只需要十几行代码就解决了核心问题而且所有逻辑一目了然。注意用户信息放入ThreadLocal后一定要在拦截器的afterCompletion方法里调用UserContext.clear()否则线程池复用会导致用户信息串到下一个请求上。这个Bug我在测试并发的时候抓过前端用户A的请求居然拿到了用户B的信息问题根源就是ThreadLocal没有清理。4.2 评价发布与校验逻辑评价发布接口是评价系统的核心链路涉及多个业务规则。我在设计接口时使用了DTO对象把前端传入的参数包装起来并用JSR-303注解做参数校验public class EvaluationCreateDTO { NotNull(message 美食ID不能为空) private Long foodId; NotNull(message 口味评分不能为空) Min(value 1, message 评分最低为1) Max(value 5, message 评分最高为5) private Integer tasteScore; NotNull(message 环境评分不能为空) Min(value 1, message 评分最低为1) Max(value 5, message 评分最高为5) private Integer environmentScore; NotNull(message 服务评分不能为空) Min(value 1, message 评分最低为1) Max(value 5, message 评分最高为5) private Integer serviceScore; Length(max 1000, message 评价内容最多1000字) private String content; }统一参数校验的Validated注解配合RestControllerAdvice全局异常处理可以有效避免在业务代码里写一堆if-else判断。这里有一个业务规则需要重点说明同一个用户对同一家美食只能评价一次。如果是重复评价就更新原有评价。这个规则在service层实现具体是先在数据库里查询是否存在user_id food_id两条维度的记录如果存在就执行更新不存在就插入。有个并发场景我一开始没考虑到用户快速点击两次提交评价产生了两个并发请求两个请求同时查出不存在历史评价结果插入了两条记录。这属于典型的并发幂等问题。解决方式有两种第一种是数据库层面加唯一索引ALTER TABLE evaluation ADD UNIQUE INDEX uk_user_food (user_id, food_id);第二种是应用层加分布式锁或同步锁。考虑到单体应用的场景加唯一索引是最简单有效的方案一次到位从数据库层面杜绝了重复数据。我在源码中保留了唯一索引并在service层做了DuplicateKeyException的捕获处理返回友好提示您已经评价过该美食重复提交会更新原评价。4.3 美食榜单的SQL设计与查询优化榜单功能是评价系统最有感觉的部分按综合评分排序展示美食列表支持按分类筛选评分相同时按评价数量倒序。对应的核心查询SELECT f.id, f.name, f.image, f.price, f.address, f.avg_score, f.evaluation_count FROM food f WHERE f.category_id ? AND f.status 1 ORDER BY f.avg_score DESC, f.evaluation_count DESC LIMIT 10;这个SQL看起来简单但在数据量上来之后有一个隐藏的排序性能问题ORDER BY f.avg_score DESC, f.evaluation_count DESC无法使用索引因为avg_score是动态更新的冗余字段值会频繁变化不适合建索引MySQL需要做文件排序filesort。我的解决方案分成两步第一步给evaluation_count和status建联合索引因为status 1是一个高选择性的过滤条件可以快速过滤掉下架的美食。第二步对于访问量最大的首页榜单增加一层Redis缓存。key是rank:category:1value是榜单JSON数据缓存时间设置为5分钟。这样即使排序慢一点也不会直接影响前端响应速度——因为大部分请求都命中缓存。关于缓存一致性因为美食评价系统对实时性要求没那么苛刻5分钟的延迟用户完全感知不到所以采用简单的定时过期策略就足够了没有必要引入Canal监听数据库binlog这种重方案。5. 数据访问层的那些坑N1问题与事务失效排查这一部分是最能体现项目实战含量的地方因为这些问题不是靠看教程就能发现的必须在真实开发中踩过才会知道。我把整个排查过程记录下来供大家参考。5.1 Spring Data JPA还是MyBatis先说选型。这个项目用的是Spring Data JPA没有用MyBatis。原因很简单项目涉及的表数量少、关联关系并不复杂JPA的自动建表、findByXxx方法派发查询、乐观锁Version支持可以极大提升开发效率。而MyBatis的优势在于复杂SQL的灵活控制适合报表、多表联查极其复杂的业务场景。本项目里的SQL基本都围绕单表和简单两表联查JPA完全能Hold住。但这里有个典型的JPA坑必须要说Entity之间的关联关系不要滥用OneToMany和ManyToOne。我第一次设计的时候给Food实体加了OneToMany(mappedBy food)来映射评价列表结果在查询美食列表时JPA自动帮我把每个美食的评价记录也查出来了——这就是教科书级别的N1问题。完整排查链路是这样的我通过foodRepository.findAll()查询10条美食记录打开show-sql日志后发现数据库执行了1条查询10条查询一共11条SQL。前1条查的是food表后10条是JPA懒加载触发的查询分别查询每个food对应的评价。数据量小的时候毫无感觉但当美食数量达到200条时一个列表接口就会产生201条SQL响应时间从50ms暴增到2秒以上。这个性能问题极其典型。解决方案有两种第一种方案是使用EntityGraph通过注解方式强制JPA使用LEFT JOIN FETCH一次性把关联数据查出来EntityGraph(attributePaths {evaluations}) Query(select f from Food f where f.categoryId :categoryId) ListFood findByCategoryWithEvaluations(Long categoryId);第二种方案是彻底放弃对象关系的懒加载把DTO查询作为主要手段通过Query写法直接联表投影到DTO对象而不是返回Entity。我在项目中最终采用了第二种方案因为它应对复杂查询更可控不会出现莫名其妙多查了几张表的问题。5.2 事务注解Transactional失效的真实排查场景Spring Boot评价系统里评价发布涉及两步写操作插入评价记录 更新美食表的评分。这两个操作必须在同一个事务里否则会出现评价记录有了但评分没更新的数据不一致问题。我一开始在EvaluationService.evaluate()方法上加Transactional。结果测试时发现评价插入成功了但美食评分没有回滚——模拟第二次写入时报错时第一次的插入操作已经提交了。这个问题的排查过程让我记忆深刻。我检查了以下三个位置第一步检查类是不是被Spring管理。Transactional要生效类必须被Spring容器托管而且方法必须通过代理调用。如果你在同一个类的内部调用evaluate()方法比如this.evaluate()事务注解就是无效的。这是最常见的失效原因。Service public class EvaluationService { public void evaluate(EvaluationCreateDTO dto) { // 这里会触发事务 createEvaluation(dto); updateFoodScore(dto.getFoodId()); } Transactional public void createEvaluation(EvaluationCreateDTO dto) { // 数据库插入 } }上面的写法createEvaluation()的事务是失效的因为外部调用是从evaluate()进入的Spring的AOP代理只对从外部进入被代理对象的方法生效内部方法调用不会走代理。第二步检查事务是否真的回滚了。默认情况下Transactional只在抛出RuntimeException时回滚受检异常checked exception不会触发回滚。Transactional public void updateFoodScore(Long foodId) throws Exception { // 抛出受检异常不会回滚 }第三步检查数据库引擎。MySQL的MyISAM引擎不支持事务InnoDB才支持。如果你的表是MyISAM任何事务配置都不会生效。这个项目里所有表我都指定了ENGINEInnoDB。最终的解决方案是把两步写操作调整到一个方法里在类外部调用确保走Spring代理同时使用Transactional(rollbackFor Exception.class)显式指定所有异常都回滚。5.3 数据库连接池耗尽问题的定位前面提过open-in-view: false的问题这里展开说说是怎么发现的。项目开发初期接口偶尔出现连接超时的报错。我检查了数据库连接池的状态发现HikariCP的活跃连接数一直居高不下而且趋势图显示连接在某个时间点直线上升。排查思路是这样的先看代码里有没有未关闭的资源。用户的Service里使用JdbcTemplate手动查询了一些统计功能个别方法没有在finally块中释放连接。JPA的用法也值得怀疑——如果实体在视图渲染过程中被懒加载连接就不会被释放。我用Arthas在线诊断工具做了线程栈分析发现大量线程卡在com.mysql.cj.jdbc.ConnectionImpl的读操作上进一步看调用栈发现是视图层在遍历美食对象时触发了评价集合的懒加载。这就印证了open-in-view: true导致Hibernate在Controller层甚至视图渲染层都保持着数据库会话一个请求从进入到响应完成连接始终被占用——在并发量上来之后连接池就原地爆炸了。设置open-in-view: false之后连接占用时间立刻大幅下降。同时我把视角层需要的关联数据在Service层就通过DTO一次性查询出来彻底断绝了在视图层触发懒加载的可能。6. 源码包完整解读与实际部署指南最后这部分聊聊源码包82480的内部结构和部署细节。对拿到源码的人来说这是最关心的事情——怎么让项目顺利跑起来。6.1 源码目录结构说明压缩包解压后核心目录如下springboot-food-rating/ ├── src/main/java/com/example/foodrating/ │ ├── FoodRatingApplication.java // 启动类 │ ├── config/ // 配置类 │ │ ├── WebConfig.java // 拦截器注册、跨域配置 │ │ └── UserContext.java // 用户上下文ThreadLocal │ ├── controller/ // 接口层 │ │ ├── AuthController.java // 登录注册接口 │ │ ├── FoodController.java // 美食信息接口 │ │ └── EvaluationController.java // 评价接口 │ ├── service/ // 业务逻辑层 │ ├── repository/ // JPA数据访问层 │ ├── entity/ // 实体类 │ ├── dto/ // 请求响应对象 │ ├── common/ // 统一响应、异常处理、工具类 │ └── interceptor/ // 登录拦截器 ├── src/main/resources/ │ ├── application.yml // 核心配置 │ ├── schema.sql // 数据库初始化脚本 │ └── data.sql // 测试数据 └── pom.xml有几个细节我特意在代码里加了注释分别是WebConfig.java里的跨域配置如果不配前端页面在8081端口调用8080接口会被浏览器拦截。GlobalExceptionHandler.java里对不同异常类型的处理顺序顺序错了会导致Detail消息被吞掉。UserContext.java的ThreadLocal清理逻辑。6.2 从零启动到跑通全流程的完整步骤如果你拿到源码想在自己电脑上跑起来按下面的顺序一步步来不会出错第一步准备环境。JDK 8或11、Maven 3.6、MySQL 5.7。这个项目不依赖Redis拦截器用的是本地Session机制简化了部署所以即使你完全没有Redis环境也能跑通。第二步创建数据库。登录MySQL后执行CREATE DATABASE IF NOT EXISTS food_rating DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后直接执行源码包里的schema.sql和data.sql里面已经包含了表结构初始化、默认管理员账号admin/admin123和几组测试美食数据。第三步修改配置文件。打开application.yml把spring.datasource.username和password改成你本机的数据库账号密码。第四步启动项目。在项目根目录执行mvn spring-boot:run或者先打包再运行mvn clean package -DskipTests java -jar target/food-rating-0.0.1-SNAPSHOT.jar启动成功后会看到Spring Boot的Logo和Tomcat started on port(s): 8080的日志。浏览器访问http://localhost:8080/food/api/food/list能看到测试美食列表数据就说明项目已经正常工作了。6.3 实际部署时容易忽略的几个细节这里分享几个部署中的隐性坑可能帮大家避免一些不必要的折腾。第一MySQL时区问题。如果你在application.yml里配置的数据库连接串没有加serverTimezoneAsia/Shanghai高版本的MySQL驱动会报The server time zone value错误。我在源码里已经加上了但如果你自己新建项目这个非常容易忘记。第二文件上传的目录问题。美食图片上传后保存到服务器本地磁盘我用了一个绝对路径配置项来指定上传目录。如果你把项目部署到Linux服务器上一定要先创建这个目录并且设置好读写权限否则上传接口会直接报FileNotFoundException。Windows开发环境默认路径和Linux不一致接手源码的人在这个地方踩坑的频率最高。第三Maven依赖的镜像配置。国内网络环境下从Maven中央仓库拉取依赖经常超时。我建议在~/.m2/settings.xml中配置阿里云镜像这个项目我实测过配置镜像后依赖下载速度能提升5倍以上。第四默认端口和context-path。项目的服务端口是8080context-path是/food。所以接口的完整访问路径是/food/api/...。如果你修改了端口或者删除了context-path前端页面的请求地址也要对应调整否则登录接口404了不要惊讶。第五前后端联调时的Cookie问题。这个项目的登录态是通过请求头Authorization传递的不是Cookie。如果你用Postman测试登录接口登录成功后会返回一个token需要在后续请求中手动加到请求头里。如果不用Header而是依赖Cookie你会遇到登录成功但访问用户信息接口还是401的诡异问题。6.4 源码交付后的测试建议最后聊聊测试。源码包里我放了一个简单的test/目录里面有几个基础的单元测试但覆盖度有限。如果你想把这个项目当成毕设或者进一步扩展建议补齐以下三类测试第一类Service层测试。重点覆盖评价发布流程包括重复评价、评分越界、美食不存在等异常场景。第二类并发测试。模拟同一用户同时提交多次评价验证唯一索引是否兜住了并发问题。第三类接口联调测试。用Postman或ApiPost把核心接口串起来跑通全流程。这是交付验收环节最常遇到的问题我自己在交付前会跑一遍完整的注册→登录→浏览美食→发布评价→修改评价→查看榜单流程确认没有任何一环断裂。在测试评分更新一致性的时候我自己写过一个小的JUnit测试先插入5条评价断言美食的avg_score正确然后更新其中一条评价的分值再断言评分跟着变了。这类测试看起来简单但它能防止最基础的数据一致性Bug被带到生产环境。7. 写在最后一个项目的价值在于可复现说句实在话美食评价管理系统在技术上并没有用到什么高深的东西Spring Boot全家桶、JPA、MySQL、拦截器每一样都是Java开发者的日常工具。但这个项目的价值在于它把一个完整的业务场景从头到尾串了起来——从数据库设计到接口开发从性能问题排查到部署上线。如果你正在学Spring Boot我的建议是不要只看教程动手把这类项目完整做一遍。重点体会几个容易忽略的地方第一事务注解为什么有时候不生效第二JPA的对象关系映射为什么会引发出乎意料的SQL第三一个简单的评分字段为什么会牵扯到数据一致性设计。如果能把这几个关键点真正想透你对Spring Boot的掌握程度会超出大多数只看视频的初学者。这套源码82480我已经整理了完整的运行说明文档数据库脚本、测试数据、部署指引都在包里。希望这篇博文的详细拆解能帮你更快地把它跑起来然后按照自己的需求去改造、扩展让这个项目真正成为你自己的作品。
阅读完成 · 觉得有帮助?
咨询建站