ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SSM框架实战:从零搭建大学生兼职系统的完整技术指南

SSM框架实战:从零搭建大学生兼职系统的完整技术指南 每年到了这个时间点计算机专业的毕设大军就开始躁动了。刷了一圈下来“基于SSM的大学生兼职系统”绝对算得上Java Web方向里点名率最高的题目之一。为什么这么多人选它因为它是一个典型的全栈业务系统有用户角色、有核心业务流、有权限控制、有数据统计难度适中不至于做不出来又足够撑起一篇像样的毕业论文。我自己带过几届学弟学妹做这个题也实际动手重构过类似的校园兼职平台今天就以这个题目为例把从技术选型到核心代码、再到答辩常见坑的完整思路一次性讲透。这里先给还没定方向的同学吃颗定心丸SSMSpring SpringMVC MyBatis虽然现在看着“老”但它是理解Java Web后端原理的最好教材之一。Spring Boot写起来舒服对面试和就业也更有用但如果你已经定了这个题目或者学校明确规定必须用SSM那这篇文章正好能帮你少走三个月弯路。同时我也把企业端、管理端、学生端的完整功能拆分和数据库设计理清楚让你拿到题目就能动手不用再对着空工程发呆。1. 项目整体设计与技术选型思路1.1 为什么选SSM而不直接上Spring Boot先解决一个最实际的问题市面上明明Spring Boot已经成主流为什么还有大量学校在毕设题目里指定SSM抛开部分学校课题库更新慢的现实原因SSM这套组合本身在“教学价值”上是无可替代的。Spring负责IoC容器和AOP事务、SpringMVC负责Web层的请求分发与参数绑定、MyBatis负责数据持久层的SQL映射三层结构边界清晰每一层都能在毕业论文里单独成章展开讲。相比之下Spring Boot的自动化配置把很多东西藏了起来写起来爽但论文里能“写”的东西反而少了。如果你是用SSM做完再花两三天转成Spring Boot版本那你会发现思路完全是通的——这就是打基础的意义。1.2 系统角色与核心业务流程拆解大学生兼职系统的本质是一个连接“有兼职需求的企业”和“有闲暇时间的学生”的双边平台。所以角色划分非常明确不要画蛇添足学生端注册登录、浏览兼职、按条件搜索、报名兼职、查看报名状态、收藏兼职、个人简历维护、历史报名记录。企业端注册登录企业资质信息、发布兼职、管理自己发布的兼职、筛选/录用报名学生、查看报名学生简历。管理员端学生账号审核与禁用、企业账号审核与禁用、兼职信息审核防止违规兼职、数据统计最常见需求兼职数量、报名人数、用户增长趋势。核心业务流程就是一条主线企业发布兼职 - 管理员审核通过 - 学生浏览搜索 - 学生报名 - 企业查看报名列表 - 企业录用/拒绝 - 学生查看结果。千万别小看这条主线的设计。很多同学一上来就急着写代码结果做到一半发现企业录用之后学生端状态没同步或者管理员审核环节根本没设计只能回头改数据库表结构非常痛苦。先把这条主流程在纸上画清楚确定每个节点的状态字段再动手建表写代码效率至少翻一倍。1.3 技术栈版本选择与兼容性避坑毕设项目最怕什么最怕环境问题消耗大量时间。我建议的版本组合是经过大量验证的稳定搭配组件推荐版本说明JDK1.8不要装太新的SSM老项目在高版本JDK下容易出现反射相关报错Maven3.6.x管理依赖别手动导jar包Spring5.1.x兼容性好配置参考资料多SpringMVC5.1.x与Spring同步MyBatis3.4.x / 3.5.x3.5以上对JDK8很友好MySQL5.7 或 8.08.0需要注意驱动class名差异下文详细说Tomcat8.5 / 9.0配合JDK8稳定IDEA任意较新版本社区版也够用这里有一个非常关键的坑MySQL 8.0的JDBC驱动用的是com.mysql.cj.jdbc.Driver而MySQL 5.x用的还是com.mysql.jdbc.Driver。很多同学从网上复制配置用的是旧驱动连新数据库必然报ClassNotFoundException。另外MySQL 8.0还必须在JDBC URL后面追加serverTimezoneAsia/Shanghai和useSSLfalse否则连接直接抛异常。# 以MySQL 8.0为例的jdbc.properties jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/parttime?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password1234562. 数据库设计与核心表结构解析2.1 六张核心表的字段设计与关联关系数据库设计直接决定你这项目的地基牢不牢。兼职系统最合理的设计是拆出六张核心表再加一到两张扩展表。我直接给出经过实践检验的表结构你可以直接用-- 用户表学生、企业、管理员统一用一张表通过role字段区分 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role TINYINT NOT NULL DEFAULT 0, -- 0学生 1企业 2管理员 phone VARCHAR(20), email VARCHAR(50), status TINYINT DEFAULT 1, -- 1正常 0禁用 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 企业信息表企业用户扩展资料 CREATE TABLE t_company ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL UNIQUE, company_name VARCHAR(100) NOT NULL, credit_code VARCHAR(50), -- 统一社会信用代码 address VARCHAR(200), contact_person VARCHAR(30), license_img VARCHAR(200), -- 营业执照图片路径 FOREIGN KEY (user_id) REFERENCES t_user(id) ); -- 兼职信息表 CREATE TABLE t_job ( id INT PRIMARY KEY AUTO_INCREMENT, company_id INT NOT NULL, title VARCHAR(100) NOT NULL, category VARCHAR(30), -- 分类家教/促销/餐饮/跑腿等 salary DECIMAL(10,2) NOT NULL, -- 薪资 salary_unit VARCHAR(20) DEFAULT 小时, -- 元/小时、元/天、元/月 work_time VARCHAR(100), -- 工作时间段描述 address VARCHAR(200), description TEXT, status TINYINT DEFAULT 0, -- 0待审核 1已发布 2已截止 3已下架 publish_time DATETIME, end_time DATETIME, -- 报名截止时间 view_count INT DEFAULT 0, FOREIGN KEY (company_id) REFERENCES t_company(id) ); -- 报名表 CREATE TABLE t_apply ( id INT PRIMARY KEY AUTO_INCREMENT, job_id INT NOT NULL, student_id INT NOT NULL, apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0, -- 0待处理 1已录用 2已拒绝 3已取消 UNIQUE KEY uk_job_student (job_id, student_id), -- 防止重复报名 FOREIGN KEY (job_id) REFERENCES t_job(id), FOREIGN KEY (student_id) REFERENCES t_user(id) ); -- 收藏表 CREATE TABLE t_favorite ( id INT PRIMARY KEY AUTO_INCREMENT, job_id INT NOT NULL, student_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_fav (job_id, student_id) ); -- 简历表 CREATE TABLE t_resume ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL UNIQUE, real_name VARCHAR(30), school VARCHAR(50), major VARCHAR(50), grade VARCHAR(20), experience TEXT, intro TEXT, update_time DATETIME );2.2 表设计里的关键细节与隐藏陷阱第一个要点是用户表不分开建。很多同学习惯建三张表存学生、企业、管理员这是典型的过度设计。这三个角色本质上都是账号只是权限不同用一张表加role字段区分既简洁又方便做统一登录。企业相关的字段公司名称、信用代码等才单独放一张扩展表通过user_id关联。第二个要点是报名表一定要加联合唯一约束。不加这个约束学生会重复报名同一个兼职逻辑上混乱答辩时老师也很容易抓住这个漏洞。UNIQUE KEY uk_job_student (job_id, student_id)的含义就是同一学生同一兼职只能有一条报名记录。后面代码里做防重复校验时这个约束是最后一道兜底防线。第三个要点是兼职信息表的状态字段设计。0待审核、1已发布、2已截止、3已下架四个状态看着简单实际开发中很容易漏判断。比如学生端查询兼职列表时必须只查status1且end_time在当天之后的包含待审核状态会让未通过的兼职提前曝光这是非常典型的低级错误。2.3 数据库初始化脚本与测试数据准备建好表之后别急着写代码先往库里塞一批仿真测试数据。这个习惯特别重要开发阶段一遍遍调试如果每次都手动插入数据会浪费大量时间。我的建议是写一个init_data.sql批量插入10家企业、100条兼职、50个学生账号、若干报名记录。测试数据要尽量“像真的”比如企业名称用“xx教育咨询有限公司”“xx餐饮管理有限公司”兼职标题用“小学英语家教”“周末超市促销员”“食堂帮工”等。这样做的另一个好处是答辩演示时界面截图好看不会一屏全是“测试1”“测试2”给答辩老师的第一印象完全不同。3. SSM整合与核心配置实操3.1 Maven依赖配置与常见依赖冲突有了表结构接下来就是把SSM框架搭起来。第一步是Maven的pom.xml。这里直接放一份我反复使用的依赖配置核心依赖是Spring相关、SpringMVC、MyBatis、MyBatis-Spring整合包、MySQL驱动、德鲁伊连接池、分页插件PageHelper、Jackson用于JSON交互!-- pom.xml 核心依赖 -- properties spring.version5.1.9.RELEASE/spring.version /properties dependencies !-- Spring核心与AOP -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-tx/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-web/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.aspectj/groupId artifactIdaspectjweaver/artifactId version1.9.4/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.6/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency !-- 数据库驱动与连接池 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.26/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.6/version /dependency !-- 分页插件 -- dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper/artifactId version5.2.0/version /dependency !-- JSON -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.9.10/version /dependency !-- Servlet与JSP -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies这里有几个很容易踩的坑我要重点说明spring-webmvc和spring-context必须版本统一否则运行时频繁报NoSuchMethodError或BeanCreationException这类错误很难排查。jackson-databind别用太新版本2.9.x和Spring 5.1.x是兼容的新版本有时会要求更高的JDK版本。分页插件PageHelper有独立的jar包不是MyBatis自带的如果漏了依赖代码里写PageHelper.startPage()会直接编译报错。3.2 核心配置文件applicationContext.xml要点拆解SSM有三个核心配置文件applicationContext.xml、spring-mvc.xml、mybatis-config.xml。其中applicationContext.xml是整合的重点包含数据源、SqlSessionFactory、事务管理器、组件扫描和Mapper扫描五大部分。!-- applicationContext.xml 核心配置 -- !-- 1. 读取数据库配置 -- context:property-placeholder locationclasspath:jdbc.properties/ !-- 2. 配置Druid连接池 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean !-- 3. 配置SqlSessionFactory -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nameconfigLocation valueclasspath:mybatis-config.xml/ !-- mapper.xml位置支持通配符 -- property namemapperLocations valueclasspath:mapper/*.xml/ !-- 实体类别名包 -- property nametypeAliasesPackage valuecom.parttime.entity/ /bean !-- 4. Mapper接口扫描 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.parttime.mapper/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean !-- 5. 事务管理器 -- bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/ !-- 6. 组件扫描排除Controller -- context:component-scan base-packagecom.parttime context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan这段配置里最值得讲的是MapperScannerConfigurer。它是MyBatis-Spring整合包提供的Mapper接口扫描器会把basePackage下所有接口自动注册为Spring Bean你才能在Service层直接用Autowired注入Mapper接口省去手写实现类的大量代码。同时注意component-scan排除了Controller——Controller由SpringMVC的子容器单独管理这样Spring父容器和SpringMVC子容器的职责才清晰不会出现事务切面作用在Controller上的诡异问题。3.3 spring-mvc.xml与web.xml的联动配置spring-mvc.xml配置了SpringMVC的视图解析器和静态资源放行。兼职系统的前端用JSP所以配置InternalResourceViewResolver指定前缀后缀。同时需要开放js、css、images等静态资源否则样式全部丢失页面难看得不行。!-- spring-mvc.xml -- !-- 开启注解驱动 -- mvc:annotation-driven/ !-- 放行静态资源 -- mvc:default-servlet-handler/ !-- 视图解析器 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean !-- Controller组件扫描 -- context:component-scan base-packagecom.parttime.controller/web.xml是Java Web项目的中枢负责配置Spring容器监听器、SpringMVC前端控制器、字符编码过滤器和欢迎页。字符编码过滤器这一项几乎所有中文乱码问题都跟它有关!-- web.xml 核心配置 -- !-- 1. Spring容器监听器 -- listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param !-- 2. 字符编码过滤器解决乱码的关键 -- filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping !-- 3. SpringMVC前端控制器 -- servlet servlet-namedispatcherServlet/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcherServlet/servlet-name url-pattern//url-pattern /servlet-mapping这里解释一个不少新手困惑的点为什么DispatcherServlet的映射写/而不是*.do这样的后缀写/是REST风格所有请求都进SpringMVC但这样静态资源也会被拦截所以配套用了mvc:default-servlet-handler放行。如果你习惯写*.do那静态资源默认不受影响但URL就会长得丑/job/list.do这种风格在现在看起来确实不够清爽。3.4 MyBatis核心配置驼峰映射与日志输出mybatis-config.xml主要做两件事打开日志和开启全局驼峰映射。!-- mybatis-config.xml -- ?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration settings !-- 日志输出到控制台 -- setting namelogImpl valueSTDOUT_LOGGING/ !-- 下划线字段映射为驼峰属性 -- setting namemapUnderscoreToCamelCase valuetrue/ /settings !-- 分页插件 -- plugins plugin interceptorcom.github.pagehelper.PageInterceptor property namehelperDialect valuemysql/ property namereasonable valuetrue/ /plugin /plugins /configurationmapUnderscoreToCamelCase开启后数据库的create_time字段可以自动映射到Java实体的createTime属性不用手写resultMap里的每个映射关系省非常多的事。日志输出到控制台也别关开发阶段看MyBatis打出的SQL能解决很多“数据怎么不对”的疑惑。4. 核心功能实现与代码细节4.1 登录认证与拦截器权限控制兼职系统有三个角色不同角色能访问的页面完全不同所以不能只做登录判断还要做角色权限判断。我推荐用SpringMVC的HandlerInterceptor实现一个登录拦截器在进入Controller之前校验用户是否登录并检查角色是否匹配。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口和静态资源 String uri request.getRequestURI(); if (uri.contains(/login) || uri.contains(/register) || uri.contains(/static/)) { return true; } // 从Session中获取登录用户 Object user request.getSession().getAttribute(loginUser); if (user null) { // 未登录Ajax请求返回401状态码页面请求重定向到登录页 if (XMLHttpRequest.equals(request.getHeader(X-Requested-With))) { response.setStatus(401); return false; } response.sendRedirect(request.getContextPath() /login); return false; } // 角色路径匹配校验 String role (String) request.getSession().getAttribute(role); if (uri.contains(/admin/) !2.equals(role)) { response.sendError(403, 无权限访问); return false; } if (uri.contains(/company/) !1.equals(role)) { response.sendError(403, 无权限访问); return false; } return true; } }然后在spring-mvc.xml里注册拦截器mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/register/ mvc:exclude-mapping path/static/**/ bean classcom.parttime.interceptor.AuthInterceptor/ /mvc:interceptor /mvc:interceptors这个设计里的一个关键点是区分Ajax请求。实际开发中页面局部刷新用的是Ajax如果直接sendRedirectAjax拿到的是整个登录页面HTML控制台报错也看不懂。加入X-Requested-With头判断后Ajax请求返回401状态码前端JS捕获后跳转登录页体验好很多。4.2 发布兼职功能的动态SQL实现企业发布兼职时提交的表单字段很多而且编辑和新增共用一套字段。这里用MyBatis的if标签做动态SQL是SSM项目里最高频的技能点!-- JobMapper.xml 动态更新 -- update idupdateJob parameterTypecom.parttime.entity.Job UPDATE t_job set if testtitle ! null and title ! title #{title},/if if testcategory ! nullcategory #{category},/if if testsalary ! nullsalary #{salary},/if if testsalaryUnit ! nullsalary_unit #{salaryUnit},/if if testworkTime ! nullwork_time #{workTime},/if if testaddress ! nulladdress #{address},/if if testdescription ! nulldescription #{description},/if if testendTime ! nullend_time #{endTime},/if if teststatus ! nullstatus #{status},/if /set WHERE id #{id} /updateset标签会自动处理最后一个逗号不用自己拼SQL。这种写法解决了“编辑页面只修改部分字段”的场景——用户没改的字段不会被写进UPDATE语句避免把原本的数据覆盖成空值。这也是MyBatis能在很长一段时间里压过Hibernate和JPA成为国内主流的原因SQL可控灵活性强。分页查询是另一个高频点用PageHelper只需要两行代码Override public PageInfoJob getJobList(JobQuery query, int pageNum, int pageSize) { // 开启分页 PageHelper.startPage(pageNum, pageSize); // 调用Mapper查询 ListJob list jobMapper.selectByCondition(query); // 封装分页结果 return new PageInfo(list); }startPage之后紧跟的第一条SQL会自动应用LIMIT分页参数不用手写数据总数和总页数都由PageInfo封装好前端JSP里直接用pageInfo.list、pageInfo.pages循环和翻页。这个插件的原理基于MyBatis拦截器在Executor执行前改写SQL了解原理之后换Spring Boot时也一样能用。4.3 报名与录用功能的并发安全问题报名功能看着简单就是往t_apply表插一条记录但实际很容易触雷并发报名。如果用户疯狂点击“立即报名”按钮前端没有禁用后端就有可能出现多条重复记录。除了数据库的联合唯一约束兜底Service层还要做一次校验Transactional public ApplyResult applyJob(Integer jobId, Integer studentId) { // 1. 校验兼职是否存在且发布中 Job job jobMapper.selectById(jobId); if (job null || job.getStatus() ! 1) { return ApplyResult.fail(兼职不存在或未发布); } // 2. 校验是否已截止 if (job.getEndTime().before(new Date())) { return ApplyResult.fail(该兼职已截止报名); } // 3. 校验是否重复报名 int count applyMapper.countByJobAndStudent(jobId, studentId); if (count 0) { return ApplyResult.fail(您已报名该兼职请勿重复操作); } // 4. 执行报名 Apply apply new Apply(); apply.setJobId(jobId); apply.setStudentId(studentId); apply.setStatus(0); applyMapper.insert(apply); // 5. 浏览量1之类的统计操作也放在这里 jobMapper.incrementApplyCount(jobId); return ApplyResult.success(); }注意这个方法加了Transactional。因为这里涉及两步数据库操作插入报名记录、更新兼职报名数任何一步失败都要整体回滚否则会出现“报名记录有了但统计数没变”的数据不一致问题。事务是SSM里Spring最核心的职责之一答辩时老师很爱问“为什么这里要加事务”你要能答出保证多个SQL操作的原子性这个关键点。4.4 管理员端数据统计实现兼职系统作为毕设数据统计功能是个巨大的加分项。不用做很复杂的大屏可视化一个按月份统计发布兼职数和报名人次的折线图就足够。数据来源用一条SQL搞定select idstatisticsMonthly resultTypemap SELECT DATE_FORMAT(publish_time, %Y-%m) AS month, COUNT(*) AS jobCount FROM t_job GROUP BY DATE_FORMAT(publish_time, %Y-%m) ORDER BY month /select拿到的ListMapString, Object直接转换成JSON传给前端用ECharts或者Chart.js渲染成图表。别自己手写一堆Java统计代码SQL能做的统计就在SQL里做简洁、高效、也好讲解。5. 常见问题与排查技巧实录5.1 前端页面到Controller的404问题现象浏览器访问http://localhost:8080/job/list界面直接404后端控制台什么都没有。排查步骤先看Tomcat启动日志是否提示spring-mvc.xml加载失败或Context initialization failed。如果有多半是配置文件的Bean定义有问题去检查spring-mvc.xml里的组件扫描路径和视图解析器。如果启动正常看web.xml里DispatcherServlet的url-pattern。写/时所有请求都进SpringMVC写/*时JSP页面本身也会被DispatcherServlet拦截导致返回的视图无法解析。检查Controller类上有没有加Controller注解、映射路径拼写是否正确。一个很常见的手误RequestMapping(/job/list)写成了RequestMapping(/job/List)大小写不对直接404。这种问题的本质是请求没有到达Controller。最快的定位方式是在Controller方法第一行加个日志输出请求到了就print没到就说明拦截器或路径映射的问题。5.2 中文乱码页面乱码和数据库乱码双重排查页面乱码的根源多半是编码不一致。先确认三个位置统一用UTF-8JSP页面顶部加% page contentTypetext/html;charsetUTF-8 languagejava %web.xml里的CharacterEncodingFilter正确配置MySQL连接URL里带characterEncodingutf8。这三处缺一不可。数据库乱码还要确认建表语句里的字符集。建表时如果没有指定DEFAULT CHARSETutf8MySQL默认用latin1中文存进去就是问号。检查方法SHOW CREATE TABLE t_job;看到CHARSETutf8才算正常。如果建表时已经错了可以用一条命令转ALTER TABLE t_job CONVERT TO CHARACTER SET utf8mb4;关于utf8mb4多说一句这是MySQL里对emoji字符的支持字符集如果项目后续要在移动端展示用户昵称、描述里含emoji用utf8mb4比utf8更稳。对于毕设项目直接全部统一成utf8mb4没毛病。5.3 MyBatis返回集合没有数据但SQL能查到现象在Navicat里执行同样的SQL能查出数据但程序返回的List是空的。原因这里最可能是resultType写错。如果SQL查询出来的列名跟实体类属性对不上而且MyBatis的mapUnderscoreToCamelCase没开或者写的是resultTypejava.util.Map就很困惑。上面我在mybatis-config.xml里强调的驼峰映射就是专门解决这类问题的。实体类字段命名与数据库列名保持“驼峰对下划线”的一致性开启映射后基本不会出问题。还有一类可能性Mapper接口方法名与mapper.xml里的id不一致、namespace写错、或者Mapper.xml文件压根没有被扫描到。检查applicationContext.xml里mapperLocations的路径是否写对比如classpath:mapper/*.xml只匹配根路径下的mapper目录如果XML放在子包里要用classpath*:mapper/**/*.xml。5.4 懒加载与NoClassDefFoundError报错如果项目里配置了MyBatis的懒加载并且在Service层返回了实体对象但关联对象没有初始化序列化或页面渲染时就可能报LazyInitializationException。对于毕设项目我的建议是关闭懒加载不做这种性能优化。虽然懒加载是MyBatis的高级特性写在论文里好看但实际开发和调试时非常容易踩坑夜半三更解决不了一个no-session异常真的会崩溃。等到工作了再学懒加载不迟。NoClassDefFoundError通常是因为依赖冲突。典型的场景是Spring 5.x要求CGLIB版本但Maven引入了旧版本。解决方式在pom.xml里用dependencyManagement统一版本号或者在Maven命令里用mvn dependency:tree查看依赖树找到冲突的jar包排除掉。6. 项目优化方向与答辩亮点6.1 完善简历模块与匹配推荐基础功能做完后可以加的亮点很多。我最推荐的是学生简历与兼职的匹配推荐。思路很简单简历里有专业、年级、经验标签兼职信息表里有分类和薪资要求做一个简单的关键词匹配按匹配度倒序展示“推荐兼职”。算法逻辑不复杂但论文里能写出一小节答辩时也能作为“系统创新点”来展示。实现上不需要复杂的推荐算法SQL就能完成-- 假设学生要推荐家教类兼职且薪资在可接受范围 SELECT * FROM t_job WHERE category 家教 AND salary 80 AND status 1 ORDER BY publish_time DESC LIMIT 10;再把“推荐结果 分类匹配 标签匹配 时间加权”的组合公式写进论文项目深度瞬间提升一个档次。6.2 移动端适配或小程序方向另外一个非常实用的优化方向是把学生端做成响应式页面或者干脆做一个微信小程序壳子。很多学校毕设里“微信小程序”是加分项因为展示效果好、贴近实际使用场景。如果做小程序版本后端的接口完全可以复用SSM的Controller只需要把返回数据改成JSON格式再封装一层HTTP API。这就是标题里“小程序”关键词的实际落点。具体做法是Controller返回ResponseBody注解的JSON数据小程序端用wx.request调用。只要保持字段名一致小程序开发很快。如果不想写小程序也可以把现有的JSP页面用Bootstrap或Layui做成响应式布局手机浏览器打开也能正常浏览。答辩演示时用手机访问页面效果比电脑截图直观得多。6.3 毕业设计论文写作建议论文结构建议按照“绪论 - 需求分析 - 系统设计 - 系统实现 - 系统测试 - 总结”来写。这里我特别提醒一个容易被批的点很多同学论文里贴了一大堆代码但没有任何说明性文字。论文不是代码仓库关键是描述“为什么这么设计”——比如为什么用三层架构为什么用MyBatis为什么状态字段要设计成四个值这些论述逻辑才是答辩老师真正关注的部分。系统测试章节也别随便写。至少要做功能测试和简单的性能测试。功能测试最好用表格列出“测试用例编号、输入、预期结果、实际结果、是否通过”看起来规范专业。性能测试可以用JMeter简单测一下登录接口和列表查询接口的QPS数据量不用很大画出并发响应时间表论文的工程性就出来了。最后说一点我个人的体会。带过那么多毕设项目发现最后出问题的往往不是技术本身而是时间管理。SSM兼职系统这个题目正常节奏是数据库设计三天、框架搭建一周、核心功能两周、测试和论文两周。别把周期拉太长也别堆到最后一个月连轴转。框架搭建阶段如果卡住了优先检查版本兼容性大部分环境问题都能通过版本对齐解决。这个项目做完你对Java Web的理解、对数据库设计的认识、对三层架构的实际落地能力会比只看视频和书本扎实太多。毕竟这是一套要陪着你走完答辩全过程的系统多做一点做得严谨一点最后站在讲台上的底气就完全不同。
返回列表