
说个我实际带过的毕设案例。有个学弟选了“基于SSM的家庭食谱管理系统”这个题结果第一版做成了“菜谱记事本”——只有登录加增删改查数据库就两张表论文凑不够字数答辩PPT也没东西可讲。后来我陪他重新梳理业务场景、补全功能模块、把代码里那些该注意的点逐一调通才算把系统做成了“能用、能讲、能答辩”的样子。这类基于SSM的Java Web管理系统的毕设在计算机毕业设计里出镜率极高因为技术栈足够经典——Spring、Spring MVC、MyBatis三件套既是学校课程重点又是面试常问的基础框架而且网上参考资料多踩坑记录也好找。但正因为它看起来“简单”很多同学反而做成了流水账式增删改查答辩时一句话就被老师问住。这篇文章我打算从项目定位、功能设计、数据库建模、核心代码实现、部署运行、答辩应对这条线完整走一遍把那些常规文档里不会明说、但实际操作中绕不开的细节都摊开讲清楚。无论你是准备拿这个题做毕设还是想用SSM练手巩固框架知识这篇文章应该能帮你少走不少弯路。1. 项目定位与整体设计思路1.1 这个系统到底解决什么问题家庭食谱管理系统字面上看是一个“管理菜谱”的系统但如果只看成“对菜谱表做增删改查”那这个项目就做窄了。毕业设计评分很看重“业务完整性”也就是说系统要能体现一个完整的使用闭环用户注册登录、浏览食谱、收藏/评论、管理员维护食谱分类与内容后台有数据统计或权限控制。这才像一个能落地的系统。实际生活中家庭食谱管理的需求确实存在家里做饭的人需要记录常做菜的做法、食材用量、烹饪耗时想控制饮食的人需要按分类查找低卡、少油、清淡的菜品家里人多了口味偏好不同分类和标签功能就显得必要。把这些日常需求抽象成系统功能就是食谱的分级分类、检索筛选、详情展示、收藏评价以及后台对食谱内容和用户的管理。这个系统里“家庭”两个字很关键意味着使用场景不是大型餐饮平台而是小范围、轻量级、以家庭成员为单位的使用所以用户角色不必做得很复杂普通用户加管理员就够用。权限不用搞细粒度但基础的登录拦截和角色区分必须有这既是业务需要也是答辩时能讲的技术亮点。1.2 为什么选SSM而不是Spring Boot我遇到过不少同学问现在企业都用Spring Boot为什么毕设还在用SSM这个问题的标准回答有两层。第一从教学角度看SSM是理解Java Web分层架构的最短路径。Spring管理对象和事务Spring MVC处理请求分发MyBatis负责数据库映射三条脉络清晰可见和Spring Boot的“自动配置”相比SSM更接近底层机制。很多学校的课程设计、实训项目仍然以SSM为主所以毕设题目延续到SSM并不奇怪。第二从答辩角度看SSM能讲的东西更多。Spring Boot把配置都“隐藏”了学生往往说不出原理而SSM里你需要手写Spring配置文件、Spring MVC配置文件、MyBatis映射文件每一行配置都可以作为答辩提问点。假如老师问“Spring IOC是什么”“MyBatis的缓存机制是怎样的”“Spring事务传播行为有哪些”在SSM项目里这些都是实物可指、有据可答的。当然SSM的缺点也很明显配置麻烦、依赖冲突多、版本兼容问题多。这些坑我在后面“常见问题与排查”部分会逐一列出。总体建议是如果学校没强制要求想省事可以用Spring Boot如果题目明确写了SSM就老老实实按SSM来做把每一个配置搞清楚答辩反而更有把握。1.3 面向的读者和使用场景这个项目适合三类人计算机相关专业毕业论文或课程设计选了类似题目的本科生尤其是Java Web方向、框架基础一般的同学。想通过一个完整项目补齐SSM实战经验的初学者不满足于单纯看教程想跑通一个真正能演示的系统。需要快速交付一个可运行项目的同学比如准备作品集、面试项目经历需要一个拿得出手的全栈演示项目。我后面写的所有内容都假设读者的基础是“学过Java基础、懂一点SQL、对SSM有概念但没完整做过项目”。如果你已经能独立写一个Spring Boot项目那这篇文章对你来说主要是查漏补缺如果你是零基础建议先花一周时间把Java Web基础Servlet、JSP、MVC模式补一补再来看这个项目否则有些概念会接不住。2. 功能拆分与模块设计2.1 用户端功能细节一个完整的家庭食谱管理系统用户端至少要包含以下模块注册与登录用户名、密码、邮箱、手机号等基本信息注册登录后才能访问收藏、评论等受限功能。菜谱浏览与分类筛选按菜品分类家常菜、汤羹、主食、凉菜等浏览支持关键字搜索。菜谱详情查看展示食材列表、调料列表、烹饪步骤、所需时间、适用人数、菜品图片等。收藏与取消收藏用户可以收藏喜欢的菜谱方便下次查看。评论与评分用户对菜谱发表评论甚至可以给出1-5星的评分。个人中心查看自己发布的菜谱、收藏列表、修改个人资料和密码。很多同学做的系统缺了“用户发布菜谱”这个功能导致普通用户只能看、不能贡献系统的互动性就弱了。我建议加上“我要分享菜谱”的入口让普通用户也能提交自己的拿手菜管理员审核后展示。这样做的好处有两个一是功能更完整数据库多一张关联表的应用场景二是答辩时老师问“你的系统数据从哪里来”你可以答“用户贡献加管理员初始化”显得考虑周全。2.2 管理端功能细节管理端是体现系统层级感的部分也是很多人容易忽略的。管理端应包含用户管理查看用户列表、启用/禁用用户、重置密码。分类管理维护菜谱分类增加、修改、删除分类。菜谱管理审核普通用户提交的菜谱可以直接编辑或删除违规内容。评论管理删除不当评论。数据概览在后台首页展示注册用户数、菜谱总数、分类数量、评论总数等基础统计。数据概览这个功能很加分虽然是简单的count查询但配上报表图之后答辩展示效果会好很多。如果不想引入ECharts直接用表格加数字展示也行如果想让项目更有亮点可以在后台引入一个轻量图表库展示近一周新增用户趋势、菜品分类占比等图表。2.3 技术选型与版本搭配SSM系统的版本搭配是个容易翻车的地方。我推荐一套比较稳妥的组合亲测兼容性良好组件推荐版本备注JDK1.8兼容性最好不要轻易用9以上Maven3.6.x管理依赖不要手拷jar包Spring5.1.x稳定教程多Spring MVC5.1.x与Spring版本保持一致MyBatis3.5.x3.4以上都行数据库MySQL 5.78.0也可以但注意驱动和时区连接池Druid 1.1.x阿里系自带监控页面前端JSP Bootstrap jQuery毕设级别够用上手快服务器Tomcat 8.5 / 9.08.5更稳JDK1.8首选这套组合的好处是教程多、文档全、网上搜报错信息基本都能搜到。不建议在毕设里追求最新版本比如Spring 6、JDK 17、Tomcat 10这些新版本改了包名javax→jakarta报错排查成本高对毕设来说完全没必要。3. 数据库设计撑起整个系统的骨架3.1 核心表结构梳理数据库设计是毕业设计的重头戏也是老师翻论文时最先看的部分。家庭食谱管理系统的表设计我建议至少包含以下六张表用户表t_user用户ID、用户名、密码、昵称、邮箱、手机号、头像、角色普通用户/管理员、状态正常/禁用、注册时间、更新时间。分类表t_category分类ID、分类名称、分类描述、排序号、创建时间。菜谱表t_recipe菜谱ID、菜谱名称、所属分类ID、封面图片、食材清单可单独建表、调料清单、烹饪步骤、烹饪耗时、难度等级、适用人数、是否审核通过、浏览量、创建人ID、创建时间、更新时间。食材表t_ingredient食材ID、菜谱ID、食材名称、用量描述。收藏表t_favorite收藏ID、用户ID、菜谱ID、收藏时间。评论表t_comment评论ID、用户ID、菜谱ID、评论内容、评分、评论时间、状态正常/删除。有的同学会把食材和调料合并成一个字段存大文本用换行符隔开这样确实表少了但对MyBatis的映射练习就少了。毕设阶段我建议把食材单独拆表实用性不一定提升多少但能体现你对数据库范式理解得更到位。调料可以直接用字段存字符串没必要为少量数据强行建表。3.2 表关系与设计要点这些表的关系很清晰分类与菜谱是一对多一个分类下有多道菜用户与收藏是多对多通过收藏表关联用户与评论是一对多菜谱与评论是一对多菜谱与食材是一对多。设计时要注意几个细节菜品状态字段推荐用t_status或is_approved0表示待审核1表示已通过2表示已驳回。很多系统只做了“已发布”和“未发布”不利于扩展。用户角色字段推荐用role0表示普通用户1表示管理员。存数字比存字符串稳妥代码里定义常量即可。逻辑删除评论表、菜谱表建议加is_deleted字段做逻辑删除而不是物理删除。原因是答辩时老师会问你“如果用户删除了评论但管理员还需要看到被删的评论怎么办”逻辑删除是个标准解法。时间字段统一用datetime类型Java实体类用java.util.Date对应避免用timestamp有2038年问题和时区问题虽然毕设场景一般不会触发但规范起见用datetime。3.3 建表SQL关键片段下面给出建表SQL的核心片段方便你对照自己手里的源码理解表结构设计CREATE TABLE t_user ( id INT(11) NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT 密码(加密存储), nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像地址, role TINYINT(4) DEFAULT 0 COMMENT 角色 0-普通用户 1-管理员, status TINYINT(4) DEFAULT 1 COMMENT 状态 0-禁用 1-正常, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE t_category ( id INT(11) NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 分类名称, description VARCHAR(255) DEFAULT NULL COMMENT 分类描述, sort INT(11) DEFAULT 0 COMMENT 排序号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜谱分类表; CREATE TABLE t_recipe ( id INT(11) NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 菜谱名称, category_id INT(11) NOT NULL COMMENT 所属分类ID, cover_image VARCHAR(255) DEFAULT NULL COMMENT 封面图片, ingredients TEXT COMMENT 食材清单(JSON或文本), steps TEXT NOT NULL COMMENT 烹饪步骤, cook_time INT(11) DEFAULT 0 COMMENT 烹饪耗时(分钟), difficulty TINYINT(4) DEFAULT 1 COMMENT 难度 1-简单 2-中等 3-困难, servings INT(11) DEFAULT 2 COMMENT 适用人数, views INT(11) DEFAULT 0 COMMENT 浏览量, status TINYINT(4) DEFAULT 0 COMMENT 状态 0-待审核 1-已通过 2-已驳回, is_deleted TINYINT(4) DEFAULT 0 COMMENT 逻辑删除 0-未删 1-已删, create_user INT(11) DEFAULT NULL COMMENT 创建人ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_id (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜谱表;特别注意MySQL 5.5及以下版本不支持CURRENT_TIMESTAMP ON UPDATE这种写法如果你用的版本较低需要在代码里手动维护更新时间。4. 核心功能实现与代码组织4.1 分层架构与包结构SSM项目的分层是固定的表现层Controller、业务层Service、持久层Mapper外加实体类entity/pojo、工具类util、公共配置config。一个合理的包结构是这样的com.family.recipe ├── controller // 控制器接收请求返回视图或JSON │ ├── UserController.java │ ├── RecipeController.java │ ├── CategoryController.java │ ├── FavoriteController.java │ └── CommentController.java ├── service // 业务接口 │ ├── UserService.java │ ├── RecipeService.java │ └── impl │ ├── UserServiceImpl.java │ └── RecipeServiceImpl.java ├── mapper // MyBatis Mapper接口 │ ├── UserMapper.java │ ├── RecipeMapper.java │ └── ... ├── entity // 实体类对应数据库表 │ ├── User.java │ ├── Recipe.java │ ├── Category.java │ ├── Favorite.java │ └── Comment.java ├── vo // 视图对象用于页面展示的组装类 │ ├── RecipeVO.java │ └── ... └── util // 工具类 ├── MD5Util.java ├── PageBean.java └── Result.javavo层很多人会忽略但它在SSM项目里很实用。比如列表页面既要显示菜谱名称又要显示分类名、创建人昵称、评论数如果直接用Entity要么写关联查询组装成VO要么在页面里写嵌套调用后者效率低且代码乱。推荐做法是Mapper里写多表关联查询把结果直接映射到VO类。4.2 登录与权限拦截的实现登录功能看似基础但有几个点做不好容易翻车。密码加密存储直接明文存密码是大忌答辩老师一定会问。使用MD5加盐方案在注册时把密码做MD5加密再存库。虽然MD5现在不算安全但作为课程项目完全够用而且实现简单。核心代码如下public class MD5Util { public static String md5(String source) { StringBuilder sb new StringBuilder(); try { MessageDigest md MessageDigest.getInstance(MD5); md.update(source.getBytes(UTF-8)); byte[] bytes md.digest(); for (byte b : bytes) { sb.append(String.format(%02x, b)); } } catch (Exception e) { e.printStackTrace(); } return sb.toString(); } public static String md5WithSalt(String source, String salt) { return md5(md5(source) salt); } }思路是先对明文做一次MD5然后加上用户名作为盐再做一次MD5。这样做的好处是即使用户名相同因为密码不同最终哈希值也不同如果只是简单MD5两个密码相同的用户会得到相同哈希值存在被字典攻击的风险。登录拦截使用Spring MVC的拦截器HandlerInterceptor。在preHandle方法里判断session中是否有登录用户public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { // 判断是否Ajax请求 String requestType request.getHeader(X-Requested-With); if (XMLHttpRequest.equals(requestType)) { response.setContentType(application/json;charsetutf-8); response.getWriter().write({\code\:401,\msg\:\未登录\}); } else { response.sendRedirect(request.getContextPath() /login); } return false; } return true; } }注意Ajax请求的处理如果页面用了Ajax调用接口后端直接redirect是无效的前端拿到的还是401状态码或无法识别所以要在拦截器里判断请求类型返回JSON让前端跳转。这个细节我见过很多项目踩坑。配置拦截器时要记得放行登录页面、注册接口、静态资源mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/register/ mvc:exclude-mapping path/css/**/ mvc:exclude-mapping path/js/**/ mvc:exclude-mapping path/images/**/ /mvc:interceptor /mvc:interceptors管理员权限校验同理再加一个拦截器判断session里的用户role是否为1。如果你只写一个拦截器在preHandle里判断路径前缀如/admin/**也可以但要注意放行用户端的/admin请求时不至于误伤。4.3 菜谱查询与分类筛选的实现查询功能是SSM项目的核心展示项也是面试常问的MyBatis应用点。我建议用MyBatis的if标签做动态SQL实现多条件组合查询select idqueryRecipeList parameterTypemap resultTypecom.family.recipe.vo.RecipeVO SELECT r.id, r.name, r.cover_image, r.cook_time, r.difficulty, r.views, c.name AS categoryName, u.nickname AS createUserName, COUNT(DISTINCT f.id) AS favoriteCount FROM t_recipe r LEFT JOIN t_category c ON r.category_id c.id LEFT JOIN t_user u ON r.create_user u.id LEFT JOIN t_favorite f ON r.id f.recipe_id where r.is_deleted 0 AND r.status 1 if testcategoryId ! null and categoryId ! AND r.category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (r.name LIKE CONCAT(%, #{keyword}, %) OR c.name LIKE CONCAT(%, #{keyword}, %)) /if /where GROUP BY r.id, r.name, r.cover_image, r.cook_time, r.difficulty, r.views, c.name, u.nickname ORDER BY r.create_time DESC /select注意GROUP BY后必须把查询列都列全否则在MySQL 5.7及以下没问题到了MySQL 8.0开启了ONLY_FULL_GROUP_BY模式会报错。如果你用MySQL 8.0记得要么关掉这个模式要么查询列都放进GROUP BY。分页通常用PageHelper插件。在pom.xml引入依赖MyBatis配置里配置插件plugins plugin interceptorcom.github.pagehelper.PageInterceptor property namehelperDialect valuemysql/ property namereasonable valuetrue/ /plugin /pluginsService层调用PageHelper.startPage(pageNum, pageSize); ListRecipeVO list recipeMapper.queryRecipeList(params); PageInfoRecipeVO pageInfo new PageInfo(list);PageHelper返回的PageInfo里有总条数、总页数、当前页等数据直接丢给前端渲染分页条极其方便。如果不想用插件也可以手写limit分页加total查询但代码量会大不少而且容易漏掉条件的同步。4.4 前端页面的落地建议前端技术栈用JSP Bootstrap就能撑住整个系统的展示需求。需要至少准备以下页面登录页login.jsp注册页register.jsp首页/菜谱列表页index.jsp菜谱详情页detail.jsp分类浏览页category.jsp个人中心页profile.jsp我的收藏页favorite.jsp后台管理页admin/index.jsp包含用户、菜谱、分类、评论管理使用Bootstrap的好处是不需要写复杂CSS组件化程度高栅格系统做响应式布局也很方便。一个常见的坑是页面之间跳转用了相对路径导致从不同层级页面跳转时路径不对。建议在JSP页面的JSTL中统一处理% String path request.getContextPath(); String basePath request.getScheme() :// request.getServerName() : request.getServerPort() path /; % base href%basePath%这样页面中所有链接直接写相对根路径即可不用再关心当前目录层级。5. 从零跑通项目的实操记录5.1 环境准备清单拿到源码后第一步不是急着导入IDE而是先把环境准备好。以下是我在实操中按顺序准备的清单安装JDK 1.8配置JAVA_HOME环境变量。安装Maven 3.6.x配置本地仓库地址建议用阿里云镜像否则下载依赖会慢到怀疑人生。安装MySQL 5.7设置root密码创建项目数据库。安装Tomcat 8.5。安装IDEA社区版或旗舰版都行旗舰版自带Tomcat集成。Maven的settings.xml里配置阿里云镜像这步非常关键。国内直连Maven中央仓库的速度很不稳定很多同学最终失败不是代码问题而是依赖下载不下来。配置方式是在mirrors节点里加mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror5.2 导入项目与配置数据库IDEA导入Maven项目时选择pom.xml等待依赖下载完成。这一步最容易出问题的是版本冲突和下载失败耐心等如果某个依赖一直下载不了可以在IDEA右侧Maven面板里点刷新或者删掉本地仓库对应的.lastUpdated文件重新下载。数据库初始化也比较直接在MySQL里新建数据库然后执行项目自带的recipe.sql脚本脚本一般包括建表语句和初始数据初始管理员账号、几个分类、几十条示例菜谱。如果没有脚本就用我上面给的建表SQL自己建。修改db.properties或jdbc.propertiesjdbc.urljdbc:mysql://localhost:3306/recipe_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码 jdbc.driverClasscom.mysql.jdbc.DriverMySQL 8.0的驱动是com.mysql.cj.jdbc.Driver5.7的驱动是com.mysql.jdbc.Driver不要搞混。另外serverTimezoneAsia/Shanghai一定要加否则MySQL 8.0会报时区错误。5.3 Tomcat部署与项目启动在IDEA里配置TomcatRun → Edit Configurations → 添加Tomcat Server → Local → 在Deployment标签页添加Artifact。注意两个关键设置Application context设置为/recipe表示访问路径是http://localhost:8080/recipe。Server标签页的端口默认8080如果被占用就改8081。Tomcat的VM options加上-Dfile.encodingUTF-8防止控制台中文乱码。启动后访问登录页用初始管理员账号登录后台再注册一个普通用户测试前台功能。如果顺利登录、浏览菜谱、收藏、评论这些核心链路在10分钟内应该能完整跑一遍。跑通之后建议做一次全流程回归注册新用户 → 修改个人资料 → 发布一道菜谱 → 在列表中搜索到它 → 收藏它 → 评论它 → 后台审核它 → 数据概览看到对应数字变化。这套流程既是功能自测也是答辩时的演示脚本。6. 常见问题与排查技巧实录6.1 环境与依赖类问题问题一Maven依赖下载失败项目全是红叉90%的情况是网络问题。检查Maven是否使用阿里云镜像检查IDEA里Maven配置是否指向了正确的位置看本地仓库是否有.lastUpdated结尾的残留文件有就删除后重新导入。另外一个容易忽略的点IDEA的Maven配置里的User settings file一定要指向你改过的settings.xml否则改了白改。问题二启动Tomcat后404或500先看控制台日志报的是ClassNotFoundException还是BeanCreationException。ClassNotFoundException一般是某个jar包没导入检查Maven依赖是否完整BeanCreationException多是配置文件问题最常见的是applicationContext.xml里包扫描路径写错或者MyBatis的mapper.xml文件没有被扫描到。检查一下target/classes目录下有没有编译后的xml文件如果mapper.xml在src/main/java目录下而没被编译进去需要在pom.xml里加build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources /build问题三访问JSP页面报“Unable to compile class for JSP”JSP编译错误通常是页面中使用了不存在的方法或类型。检查页面引入的JSTL标签库URL是否正确检查EL表达式的属性名是否和实体类getter对应。6.2 数据库与MyBatis映射问题问题四页面显示乱码这是我最常被问的问题之一。乱码有两种页面中文乱码数据库存储乱码。页面乱码检查JSP头部是否有pageEncodingUTF-8检查Tomcat的URIEncoding是否设置为UTF-8数据乱码检查MySQL连接串是否带characterEncodingutf8检查数据库表是不是utf8mb4字符集。还有一个小坑IDEA控制台乱码和浏览器乱码不是同一种问题。控制台乱码在Tomcat VM options里加-Dfile.encodingUTF-8解决浏览器乱码要排查请求和响应编码。问题五MyBatis的Mapper接口找不到或绑定异常检查mapper接口的全限定名和mapper.xml的namespace是否一致检查Mapper接口的方法名和mapper.xml里statement的id是否一致。这个异常信息通常说得很清楚按提示检查即可。问题六查询列表时分页数据不准PageHelper分页出错最常见的原因是在PageHelper.startPage()和查询之间夹了其他SQL操作。正确的用法是startPage紧跟着要分页的查询语句中间不要插任何东西。6.3 答辩常见追问与应对这个项目答辩时老师通常围绕以下方向提问“为什么选择SSM”这是一个基本问题答三层Spring负责对象管理Spring MVC负责请求分发和视图控制MyBatis负责数据库操作。三者各司其职组合起来适合中小型Web应用开发。可以简单对比Spring BootSSM需要显式配置能更好理解框架原理。“你的系统里Spring事务是怎么配置的”在applicationContext.xml里配置DataSourceTransactionManager开启注解事务驱动然后在Service实现类上使用Transactional注解。核心点是被代理的Service方法调用时事务才会生效方法内部自调用事务注解不生效这是经典考点。“MyBatis和JDBC有什么区别”JDBC是Java操作数据库的底层规范MyBatis是在其基础上封装的持久层框架解决了参数手动设置、结果集手动映射、SQL与代码耦合的问题。使用Mapper接口加XMLSQL可以独立维护动态SQL处理复杂的查询条件也方便。“用户密码为什么用MD5加盐”直接明文存数据库一旦数据库泄露所有账号密码都暴露简单MD5可以被彩虹表反查。加盐是额外拼接一个随机字符串后再做哈希大大增加破解成本。如果老师追问MD5的安全性问题可以补充说项目中如果有更高安全需求可以用BCrypt等更现代化的算法。“你系统里的表是怎么设计的为什么这么设计”回答思路是先说有几张表和它们的功能再说表和表之间的一对多、多对多关系然后解释设计时考虑了范式、逻辑删除、索引等。如果老师追问“为什么不做成一张表”可以从职责单一、扩展性、数据一致性几个角度回答。6.4 独家避坑心得我实际操作中踩过几个比较隐蔽的坑分享出来。第一IDEA的Tomcat集成部署时如果没有配置“Build Artifact on Update”修改了类文件或页面后点刷新不生效老是看到旧页面。建议在Tomcat配置的On Update action选择“Redeploy”On frame deactivation选择“Update resources”。第二Cookie和Session的坑。使用Chrome浏览器测试时如果开启了“阻止第三方Cookie”登录后刷新页面又回到登录页排查好久还以为是代码问题结果换个浏览器就好了。这类页面测试问题DevTools的Network面板看请求是否带了Cookie一看便知。第三上传菜谱封面图时如果项目部署在Tomcat的webapps目录下上传的图片在重启Tomcat后会被清掉。解决方法把图片存储路径配置到Tomcat外部的绝对路径再用虚拟映射方式暴露给前端或者在Tomcat的server.xml中配置虚拟目录。毕设阶段最简单的方案是预置一张默认图片上传功能不强制实现但实现后体验确实提升一截。第四也是我很想强调的一点很多同学项目跑通后就算结束了不写README、不录演示视频、不整理答辩稿。但毕设成绩里的印象分很重要一个能流畅演示、代码目录清晰、配置说明完整的项目比一个功能更多但乱糟糟的项目更受老师认可。建议在项目根目录放一个README.md写清数据库账号密码、启动步骤、测试账号、功能清单这是性价比极高的加分项。7. 项目扩展方向与实用建议这部分与其说是扩展不如说是给学有余力的同学一个“增值”指南。毕设评分里创新点往往是拉开差距的关键但创新不一定要用多高深的技术把常见功能做得更顺手就是亮点。7.1 可落地的低风险增强功能第一个推荐加的是“菜谱评分汇总展示”。在详情页展示每个菜谱的平均分和评分人数虽然本质是SQL的AVG和COUNT聚合查询但视觉效果好面试也好讲。第二个推荐加的是“热门菜谱排行榜”。根据浏览量或收藏量排序取前10名展示在首页侧边栏。这也是简单的排序查询但对用户体验提升很明显。第三个推荐加的是“食材反查功能”。输入“土豆”就能查所有包含土豆的菜谱。实现上需要把食材清单从大文本改成可拆分的数据结构可以用JSON格式存储食材数组然后用LIKE查询匹配或者拆成食材子表。这个功能的业务价值很直观冰箱里剩什么就搜什么能做什么菜一目了然。7.2 如果想进一步挑战如果时间和能力允许可以考虑引入Redis做缓存热点菜谱引入Elasticsearch做全文检索甚至做一个简单的推荐系统——根据用户收藏分类偏好推送相似菜谱。但我要提醒一句毕设的重心永远是“完整”和“自洽”不是“炫技”。一个功能齐全、代码规范、论文逻辑清晰的项目得分一定高于一个功能花哨但bug满天飞的项目。高层级的技术栈可以写进论文的展望部分但不要把核心功能寄托在新技术上否则稳定性没有保障答辩风险也大。7.3 关于“附源码”资源的使用建议很多同学拿到“附源码”的项目第一反应是赶紧跑起来然后就不管了。我的建议完全反过来先读代码再改代码最后才跑代码。拿到项目后先看数据库脚本理解表结构再看pom.xml理解依赖了哪些框架然后看Spring和MyBatis的配置文件理解关键Bean的装配最后看Controller和Mapper理解一个请求从进入系统到返回页面的完整链路。这样花两天时间通读比直接运行一周学到的都多。然后在读懂的基础上做“微整形”改个页面样式、加一个小功能、调整一段校验逻辑让它不完全等于原始模板。这不只是为了避免查重更重要的是只有你自己改过的代码答辩时你才敢说“这是我做的”。老师在答辩现场最反感的就是学生拿着跑起来的项目却说不清楚代码细节。我个人的体会是SSM这类经典框架项目最大的价值不在于技术多新而在于它能帮你把“分层”、“解耦”、“ORM”、“IOC”、“AOP”、“事务”这些课上学过但总是悬在空中的概念真正落地到能运行的代码里。把每一条配置、每一个注解为什么这样写搞清楚你的收获比拿到一个“高分毕设”要重要得多。最后再做个小提醒运行项目时如果发现某个页面样式错乱先看浏览器的Network面板看是不是CSS或JS文件加载404如果后台管理页面打不开先看拦截器是否把管理员的session校验写反了。控制台的报错信息永远是第一排查线索不要一上来就怀疑项目源码有问题。祝你顺利把这个项目跑通、吃透然后带着它自信地去答辩。