简介:这套软件工程课程设计资源是一份基于SSM框架的机票/火车票预定系统完整项目,面向软件工程专业学生、Java Web开发者以及需要课程设计完整方案的初学者。压缩包共1651个文件,总大小约50.16MB,涵盖636个js脚本、354个png图片、204个css样式、51个java源文件、37个jar依赖、85个xml配置,并包含jsp页面、properties配置和数据库sql脚本,目录结构清晰,可直接导入IDE进行部署运行。系统实现了用户注册登录、航班/车次检索、在线预订、订单与后台管理等核心模块,完整展示了Spring容器管理、SpringMVC请求流转、MyBatis动态SQL与事务控制的协同机制。已有273人学习下载,适合用来理解企业级SSM整合流程,也可作为课程设计报告、答辩演示和进一步功能扩展的参考基线。
1. 软件工程课设里的“标准答案”:机票/火车票预定系统到底值不值得做
很多人做软件工程课程设计时,最纠结的不是写代码,而是选什么题目。做得太简单,报告没东西写;做得太复杂,一个人又扛不住。机票/火车票预定系统属于那种“看起来难、做起来顺、演示效果好”的题目——它有天然的流程闭环:用户注册、登录、查询班次、下单、扣减余票、后台管理,业务逻辑比普通的增删改查好看,但难度又没有超出SSM框架的范畴。这份基于SSM框架的预定系统源码,适合正在选课设题目的人,也适合想拿一套完整SSM项目来练手的人。它能解决最典型的痛点:不用从零搭架构,花一个晚上就能把项目跑起来,再花几天改成自己的东西。
2. 拆解技术底座:Spring、SpringMVC、MyBatis在预定系统里各自扮演什么角色
很多同学在跑SSM项目时,把代码跑通了,但别人问一句“请求是怎么走到数据库的”就卡住了。这不怪你,SSM是三个框架拼在一起的,任何一个环节都是黑匣子,跑通了容易,讲清楚难。但课程设计答辩恰恰喜欢问这个。所以这一章先把三个框架在这个系统里的分工讲透,你再去看代码,就是一层窗户纸。
2.1 Spring与SpringMVC:一个请求从浏览器到数据库的完整链路
我一般会这样给人讲:Spring是管家,SpringMVC是前台,MyBatis是跑腿的。管家负责把各种对象创建好、管理好,比如Service对象、Mapper对象,不用你自己new;前台负责接客,用户点了一个按钮,请求先到前台,前台核对路径、找到对应的Controller;Controller再把活儿派给Service,Service如果需要查数据库,就叫MyBatis跑腿。
具体到这份预定系统里,用户提交登录表单时,请求链条是这样的:
浏览器 POST /user/login ↓ DispatcherServlet(前端控制器) ↓ HandlerMapping 找到 UserController 的 login 方法 ↓ UserController 调用 UserService ↓ UserService 调用 UserMapper 接口 ↓ MyBatis 执行 UserMapper.xml 里的 selectUserByUsername SQL ↓ 从 MySQL 返回结果,逐层回传,最后跳转到首页或返回错误提示SpringMVC的核心是DispatcherServlet,它在一个请求进来时做三件事:根据URL找到对应的Controller方法(HandlerMapping),调用方法并传入参数(HandlerAdapter),根据方法返回值决定跳转到哪个页面或返回什么JSON(ViewResolver)。你在spring-mvc.xml里看到的内容,其实就是把这三件事的规则告诉它。
Spring在这套系统里管得更底层一些:数据源连接、Service实例、事务管理器。你在spring-mybatis.xml里能看到<context:component-scan>,它的作用是把包下所有标了@Service、@Controller、@Repository的类扫描进容器,由容器统一创建和维护。这解决了一个实际问题:不同层之间的对象依赖,不需要在每个类里手动实例化,容器自动就装配好了。
2.2 MyBatis:SQL与Java对象之间的映射如何建立
MyBatis在这套系统里承担的是最枯燥但最关键的活:把Java方法调用翻译成SQL,再把SQL查询结果翻译回Java对象。它有一个硬性约定:一个Mapper接口对应一个XML文件,接口方法名对应XML里的语句ID,参数类型和返回类型要写对。
比如用户登录时,你的UserMapper接口里写了这样一个方法:
public interface UserMapper { User selectByUsername(String username); }对应的UserMapper.xml里就必须有一个select标签,id和接口方法名一致:
<select id="selectByUsername" parameterType="java.lang.String" resultType="com.course.trainbooking.pojo.User"> SELECT id, username, password, real_name, phone FROM t_user WHERE username = #{username} </select>这里有一个最容易出错的匹配点:parameterType决定入参类型,resultType决定返回类型。如果你的User实体类里字段名是realName(驼峰),而数据库列名是real_name(下划线),MyBatis默认不会自动映射,查询出来的对象里realName就是null。这套系统提供的实体类如果出现了这种字段,通常会有一个mybatis-config.xml里的mapUnderscoreToCamelCase配置项来兜底。你拿到项目后,先去检查这个配置有没有打开,避免后续改代码时被这种细节绊住。
另外,MyBatis的动态SQL是这套系统的查询功能能写得简洁的关键。车次搜索往往有多达四五个筛选条件:出发地、目的地、出发日期、班次类型。你不可能为每种组合都写一条SQL,动态SQL标签就能按需拼接:
<select id="searchList" resultType="com.course.trainbooking.pojo.FlightTrain"> SELECT * FROM t_flight_train <where> <if test="departure != null and departure != ''"> AND departure = #{departure} </if> <if test="arrival != null and arrival != ''"> AND arrival = #{arrival} </if> <if test="departDate != null"> AND DATE_FORMAT(depart_time, '%Y-%m-%d') = #{departDate} </if> </where> ORDER BY depart_time </select><where>标签很聪明,它会在第一个条件前自动去掉多余的AND,如果三个条件都没传,这条SQL就退化成SELECT * FROM t_flight_train ORDER BY depart_time,不会因为空条件报语法错。
2.3 为什么课设选SSM而不是Spring Boot
这是答辩必问题,也是我做技术选型时首先要跟同学确认的事。用Spring Boot做这类系统当然更快,但它把配置都自动完成了,你的课设报告里写不出多少东西。SSM的“麻烦”恰恰是它的教学价值:你需要手写web.xml、手写spring-mvc.xml、手写spring-mybatis.xml,这个过程让你真实地接触到框架之间如何衔接。答辩时老师问你“Spring容器是怎么启动的”,你说得出ContextLoaderListener;问你“事务加在哪一层”,你答得出Service层。换成Spring Boot,很多学生只认识注解,底层一问就卡壳。
从工程结构上看,这套系统也是标准的四层拆分:controller层接收请求、service层写业务逻辑、mapper层写SQL、pojo层定义实体。我现在看任何SSM项目,第一件事就是打开包结构,看它是不是这四层清晰分开的。如果是,说明写这份代码的人有工程意识,后续改功能时你知道去哪个包里找文件。如果包结构乱成一锅粥,那跑通归跑通,你答辩时很难说清楚。这份资源在这点上做得比较规矩,你可以直接拿它的包结构当模板用。
3. 从零跑起来:环境版本、数据库脚本与配置文件的一步步折腾
拿到一套SSM源码,最让人头疼的不是写代码,而是第一次启动时的环境问题。我见过太多人卡在版本冲突上半天起步不来。这一章把从导入项目到浏览器看到登录页的完整过程拆开,每一步都给你能照着做的操作。
3.1 环境与版本:先把依赖矩阵固定下来
SSM项目对环境版本比较挑剔。先别急着下载最新的JDK或Tomcat,这套课设级别的系统通常基于下面这套老而稳的组合(我一般推荐同学在本地按这个固定配置装环境,能省掉一半的报错):
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | SSM最兼容的版本,新版JDK容易遇到加密或反射类报错 |
| Tomcat | 8.5 | 支持Servlet 3.1,IDEA中配置最省事 |
| Maven | 3.6.x | 3.9以上版本时建议换用IDEA自带的Bundled Maven |
| MySQL | 5.7 | 5.5太老,8.0以上要注意驱动和时区设置 |
| Spring | 4.3.x / 5.x | pom.xml里写什么你就用什么,不要自己乱加版本 |
| MyBatis | 3.4.x | 配合mybatis-spring 1.3.x,接口扫描最稳 |
注意一个习惯:拿到项目先打开pom.xml,看它锁定的Spring和MyBatis版本,不要用IDEA提示的“最新版本”去替换。我记得有一次帮A同学调项目,IDEA推荐他把MyBatis升到3.5版本,结果启动时直接报TypeException,回退到3.4就流畅运行了。
Maven镜像也建议提前配好,否则下载依赖时会卡到想放弃。在~/.m2/settings.xml里加一个阿里云镜像仓库地址,下载速度会快很多,这个属于通用基础配置,项目本身帮不了你。
3.2 项目结构与数据库初始化:先看目录,再导脚本
拿到压缩包解压后,先看目录结构,不要急着点运行。典型的SSM项目结构是Maven的webapp标准布局:
train-booking-system ├── pom.xml # Maven依赖管理 ├── sql │ └── train_booking_db.sql # 建库建表脚本+初始数据 └── src/main ├── java/com/course/trainbooking │ ├── controller # 控制器层(用户、订单、管理员) │ ├── service # 业务接口与实现 │ ├── mapper # MyBatis Mapper接口 │ ├── pojo # 实体类(User、FlightTrain、Orders等) │ ├── interceptor # 登录拦截器 │ └── util # 工具类(订单号生成等) ├── resources │ ├── jdbc.properties # 数据库连接配置 │ ├── spring-mybatis.xml # Spring与MyBatis整合配置 │ ├── spring-mvc.xml # SpringMVC配置 │ ├── mybatis-config.xml # MyBatis全局配置 │ └── mapper # Mapper的XML文件 └── webapp ├── WEB-INF/web.xml # 部署描述文件 └── jsp/css/js # 页面与静态资源打开数据库管理工具,执行sql目录下的train_booking_db.sql。执行脚本前先确认你用的字符集是utf8mb4,否则后面中文存进数据库会显示成问号。这个脚本会创建四个核心表:用户表t_user、车次/航班表t_flight_train、订单表t_orders、管理员表t_admin,并插入几条测试班次数据。订单表里故意设计了一个冗余字段total_price,因为用户下单后即使价格调整,订单里的金额也不能跟着变,这是订单系统的常识,课设报告里可以提一笔。
3.3 配置文件改造:改哪里、为什么要改
项目要跑起来,需要动的地方其实就四个文件:JDBC连接、web.xml、spring-mybatis.xml、spring-mvc.xml。先说最容易出问题的JDBC配置:
# jdbc.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/train_booking_db?useSSL=false&characterEncoding=utf8 jdbc.username=root jdbc.password=123456注意两点:MySQL 5.7用com.mysql.jdbc.Driver没问题,如果你把数据库换成了MySQL 8.0,驱动类要改成com.mysql.cj.jdbc.Driver,并且连接串里要加serverTimezone=Asia/Shanghai,不然查询会报时区错误。密码改成你自己本机的数据库密码即可。
然后是web.xml里的编码过滤器,这个过滤器必须放在所有过滤器的最前面,否则POST请求的中文参数在到达Controller之前就已经乱码了:
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>为什么一写就死?因为Tomcat默认使用ISO-8859-1解码POST请求体,如果你不在最早的位置把编码切到UTF-8,后面的Spring配置做得再对也白搭。
接着检查spring-mybatis.xml,重点确认两处:数据源的jdbc:url用的连接串是jdbc.properties里的,mapperLocations指向classpath:mapper/*.xml。最后在spring-mvc.xml里确认视图解析器前缀是/WEB-INF/jsp/,后缀是.jsp。这样可以保证Controller返回login字符串时,真正加载到的是/WEB-INF/jsp/login.jsp文件。
3.4 部署启动与登录验证:怎么确认项目真正跑起来了
在IDEA中配置好Tomcat后,将项目部署到Tomcat(选择war exploded方式,方便改代码后热部署)。启动后访问http://localhost:8080/,如果能看到登录页,说明基础链路已经通了,但还不够——接着做两件事验证:先注册一个新用户,再退出重新登录。这一步能同时验证数据库写入是否成功、Session是否正常保存用户信息、密码校验是否生效。
如果启动过程中报错,先看控制台第一个异常栈,不要看最后一行。有一次项目报错说“找不到驱动类”,结果是因为pom.xml里的mysql-connector-java依赖没加载进来;还有一次报“端口占用”,是之前启动的Tomcat进程没结束。90%的SSM启动失败都是这几类问题,下面专门给你列避坑清单。
4. 核心业务实现:登录拦截、动态查询、下单事务与后台管理
系统跑通之后,你就要开始“读代码”和“改代码”了。课设答辩时老师不会挨个文件问,但一定会问几个核心功能的实现细节:怎么控制用户权限的、怎么防超卖的、多条件查询怎么写的。这章直接对应这几个问题,把代码落点和设计要点过一遍。
4.1 登录状态管理:Session与拦截器的配合方式
这套系统有普通用户和管理员两种身份,对应的权限也不同。最简单的做法就是写一个拦截器,在请求进入Controller之前检查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) { // 未登录,重定向到登录页 response.sendRedirect(request.getContextPath() + "/"); return false; } return true; // 已登录,放行 } }这段代码的逻辑非常直接:preHandle拦截所有请求,检查Session中是否有loginUser这个键,没有就重定向到登录页。这里有个工程上的习惯问题,我见过很多同学把权限判断写在每个Controller方法里,代码重复不说,哪天漏写一个方法,权限就崩了。用拦截器集中控制才是规范做法。
拦截器写完不算完,还要在spring-mvc.xml里注册,并且配置拦截路径:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/user/**"/> <mvc:mapping path="/order/**"/> <mvc:exclude-mapping path="/user/login"/> <mvc:exclude-mapping path="/user/register"/> <mvc:interceptor-ref bean="loginInterceptor"/> </mvc:interceptor> </mvc:interceptors>这里要特别留神exclude-mapping的配置——登录和注册请求本身是不需要登录的,如果漏配,就会出现一个“死循环”:用户还没登录→被拦截器重定向到登录页→登录页又被拦截器重定向到登录页。曾经有个同学问我,为什么他的项目一访问就无限跳转,最后发现就是这个路径被同一条拦截规则罩住了。
4.2 班次查询:Service层拼接条件,Mapper层动态SQL
查询功能是预定系统的门面。用户在前端选择出发城市、到达城市、出发日期,点击查询后,前端通过Ajax把三个参数传到后端,Controller组装这些参数,Service层不做SQL拼接,只负责把参数原样传给Mapper——SQL拼接统一放在Mapper的XML里,这样做的好处是SQL可读、可维护、不掺Java代码。
Controller层是这样接收参数的:
@Controller @RequestMapping("/search") public class SearchController { @Autowired private FlightTrainService flightTrainService; @RequestMapping("/list") @ResponseBody public List<FlightTrain> list(String departure, String arrival, String departDate) { return flightTrainService.search(departure, arrival, departDate); } }这里用了@ResponseBody直接返回JSON,前端页面用jQuery的$.ajax接收后渲染到表格里,不用刷新整个页面,体验更好,课设报告里也可以当作一个亮点写。
前端Ajax请求是标准的jQuery写法:
$.ajax({ url: "/search/list", type: "GET", data: { departure: $("#departure").val(), arrival: $("#arrival").val(), departDate: $("#departDate").val() }, dataType: "json", success: function (res) { // 清空表格、遍历res、动态追加行 renderTable(res); }, error: function () { alert("查询失败"); } });前端传过来的参数未必全都有值,所以Mapper必须用动态SQL来兜底,这个在前面已经展示过<where>写法。这里再提一个细节:departDate如果传空了,DATE_FORMAT(depart_time, '%Y-%m-%d') = #{departDate}这个条件就不拼接,查询会返回所有班次;但如果传了值,这个写法能保证按日期精确匹配,不受库存量影响。
4.3 下单核心流程:事务边界与余票扣减逻辑
下单是整个系统里最容易出问题的地方。用户点了“下单”按钮后,系统要同时干三件事:检查这趟班次余票是不是充足、扣除余票数量、生成一条订单记录。这三步必须绑定在同一个事务里——如果只扣了库存但订单没生成成功,余票就白白消失了;反过来,订单生成了但库存没扣,就会出现“超卖”。
下面是标准的Service层下单方法(我把几个关键步骤内联在一起,便于讲事务边界):
@Service public class OrderServiceImpl implements OrderService { @Autowired private FlightTrainMapper flightTrainMapper; @Autowired private OrdersMapper ordersMapper; @Transactional(rollbackFor = Exception.class) public boolean createOrder(String username, Integer ftId, Integer quantity) { // 1. 查询航班/车次,锁定当前数据 FlightTrain ft = flightTrainMapper.selectByIdForUpdate(ftId); if (ft == null) { throw new BusinessException("班次不存在"); } // 2. 检查余票 if (ft.getStock() < quantity) { throw new BusinessException("余票不足,当前余票:" + ft.getStock()); } // 3. 扣减余票(条件更新防并发) int rows = flightTrainMapper.deductStock(ftId, quantity); if (rows == 0) { throw new BusinessException("下单失败,余票已售罄"); } // 4. 生成订单 Orders order = new Orders(); order.setOrderNo(generateOrderNo()); order.setUsername(username); order.setFtId(ftId); order.setQuantity(quantity); order.setTotalPrice(ft.getPrice() * quantity); order.setStatus("已支付"); return ordersMapper.insert(order) > 0; } }几个关键点值得在答辩时讲:
@Transactional(rollbackFor = Exception.class)把整个方法包进事务,任何一步抛异常都会回滚,保证“库存和订单同生共死”。我用rollbackFor = Exception.class而不是默认设置,是因为默认情况下事务只对RuntimeException回滚,如果自己封装的异常不是运行时异常,事务就不会回滚,这是很多同学在课设里翻车的根源。
第一步用selectByIdForUpdate,在MySQL里加上FOR UPDATE会锁定这行记录,直到事务结束才释放。这个锁防止两个用户同时下单同一趟班次的余票被重复扣减。
第三步的deductStock是一个关键SQL,它的巧妙之处在于用条件更新代替了纯Java判断:
UPDATE t_flight_train SET stock = stock - #{quantity} WHERE id = #{ftId} AND stock >= #{quantity}这个SQL自带校验:如果当前库存小于购买数量,影响行数为0,代码里的rows == 0判断就会生效。它比“先查出来再判断再更新”的方式更安全,因为数据库层面的行锁和条件判断把并发问题挡在了SQL层面。
实际运行效果是:两个用户同时买最后一张票,数据库会让两条UPDATE串行执行,第二条因为stock >= 1不满足而更新0行,从而只生成一个订单。这段话你写进课设报告的“核心模块设计”章节,分数不会低。
4.4 后台管理:班次发布与订单处理
后台部分通常包含管理员登录、班次新增和编辑、订单查看和状态更新。管理员和用户共用同一个登录入口,但登录后走不同的Session标识,这一点在代码里是通过loginUser和loginAdmin两个不同键来区分的。后台控制器通常放在/admin/**下面,注册一个专门拦截/admin/**的拦截器,和前台用户拦截器互相独立,避免用户登录后能直接访问管理员页面。
新增班次的Controller方法很普通,就是把前端表单数据绑定到FlightTrain对象上,再调Service层进行入库:
@RequestMapping("/add") public String add(FlightTrain ft) { flightTrainService.save(ft); return "redirect:/admin/list"; }这里有个小技巧:前端表单的name属性要和实体类的字段名一致,比如<input name="departure">对应FlightTrain里的departure属性,SpringMVC会自动完成参数绑定。如果字段对不上,属性就是null,入库时SQL就会报“列不能为null”的错误。我见过一个同学把前端的“出发地”name写成了from,而后端字段叫departure,结果新增班次一直失败,改个名字就通了。
5. 避坑指南:SSM预定系统跑通过程中最常翻车的五个问题
这一章是拿真金白银换来的经验。我把做课设辅导时同学问得最多、最容易卡住的五个问题写出来,每条都按“现象→原因→解决”的顺序给,你可以把这张清单当作排错手册。
5.1 启动报错:Invalid bound statement (not found)
现象:项目启动后一调用任何Mapper方法,控制台就抛Invalid bound statement (not found),提示找不到某个Mapper接口的方法。
原因:MyBatis把Mapper接口和XML文件绑定失败。最常见的原因有两个:一是UserMapper.xml里的namespace写错了,没有指向UserMapper接口的完整类名;二是spring-mybatis.xml里的mapperLocations没有扫描到mapper目录下的XML文件。
解决:先检查每个Mapper XML顶部的namespace是不是com.course.trainbooking.mapper.UserMapper这样的完整路径。再检查spring-mybatis.xml里有没有这段配置:
<property name="mapperLocations" value="classpath:mapper/*.xml"/>如果没有,说明XML一个都没加载进去,接口自然找不到SQL语句。还有一个隐蔽问题:如果pom.xml没有把src/main/resources/mapper目录里的XML文件打进去,IDEA启动时会把XML文件忽略掉,这时需要在pom.xml里加一段<resources>配置,显式把**/*.xml纳入构建范围。
5.2 数据库中文全变成问号
现象:注册用户时明明填的是汉字,存到MySQL里变成???,或者页面上从数据库读出来的中文全部乱码。
原因:三层字符集不一致。第一层是请求进入Tomcat时用的编码,第二层是JDBC传输数据的编码,第三层是MySQL表本身的字符集。任何一层不匹配,中文数据就会出问题。
解决:请求层在web.xml里加CharacterEncodingFilter,这点前面强调过;JDBC连接串加characterEncoding=utf8;MySQL建库时指定字符集,如果库已经建了,执行下面SQL把表转成utf8mb4:
ALTER TABLE t_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;改完之后重启项目,再注册一个新用户测试。如果老的乱码数据还在,说明是建表时就已经错了,需要清掉表重新导入脚本。从工程习惯来说,我建议你把train_booking_db.sql脚本打开看一眼建表语句,有没有带DEFAULT CHARSET=utf8mb4,没有就自己加上再导库。
5.3 页面样式全没了:CSS和JS返回404
现象:页面能打开,但没有任何样式,浏览器F12控制台全是CSS和JS文件的404错误。
原因:SpringMVC的DispatcherServlet把URL前缀/css/**、/js/**的请求也拦截了。项目里往往没有这些静态资源对应的Controller,所以返回404。
解决:在spring-mvc.xml中放行静态资源目录:
<mvc:default-servlet-handler/> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:resources mapping="/js/**" location="/js/"/> <mvc:resources mapping="/images/**" location="/images/"/>mvc:resources的含义是:当URL以/css/开头时,去webapp/css/目录下找文件。如果你用的是Maven标准结构,确认你压缩包里解出来的CSS、JS文件是在src/main/webapp/css目录下,而不是扔在src/main/java目录里。
5.4 下单成功但库存没变:事务没生效
现象:页面提示下单成功,订单表里也有数据,但班次的库存数量没有减少。
原因:@Transactional注解写在了Controller层方法上,或者Service实现类没被Spring扫描到。
解决:事务必须写在Service实现类的方法上,不能让Controller去背这个责任。Controller是请求转发层,它不是Spring事务管理器管理的对象。另外检查spring-mybatis.xml里有没有配置DataSourceTransactionManager,以及有没有开启注解事务驱动:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>缺了DataSourceTransactionManager,@Transactional就是个无效注解,Spring不知道要拿哪个事务管理器来执行回滚。
5.5 前端传日期字符串,后端报400错误
现象:查询班次时,如果带了日期参数,后端直接报400 Bad Request;去掉日期参数就正常。
原因:Controller方法接收String departDate没问题,但如果实体类里有java.util.Date类型的属性,SpringMVC在把字符串转成Date对象时需要知道日期格式,默认格式跟页面传的不匹配,转换失败就报400。
解决:在实体类的日期字段上加@DateTimeFormat注解:
@DateTimeFormat(pattern = "yyyy-MM-dd") private Date departTime;同时在前端input框的value值保持yyyy-MM-dd格式。如果你的班次时间精确到时分秒,可以按yyyy-MM-dd HH:mm:ss写,但要注意前端传值时不能漏掉空格和冒号。这类问题还有个更快的定位方式:查看Tomcat控制台的完整异常栈,找到MethodArgumentTypeMismatchException,它会把期望的类型和实际传入的值写得清清楚楚。
6. 课设验收与答辩:演示路径、报告图纸与高频问题一次讲清
代码跑通只算完成了一半,课设的分数一半在报告、一半在答辩。这一章讲怎么把你做的工作更好地展示出来,同时避免在答辩时被问倒。
6.1 课设报告:图比代码更有说服力
老师在课设报告里最想看到的是设计过程,不是大段代码粘贴。报告至少要有三张图:用例图(用户能做什么、管理员能做什么)、ER图(四张表之间的关系)、系统架构图(浏览器、Controller、Service、Mapper、MySQL的分层关系)。ER图尤其重要,它直接展示了数据表之间的外键关系——订单表引用用户表和班次表,这一笔要讲清楚,体现出你的数据库设计不是拍脑袋来的。
代码方面,挑两个核心模块放少量代码就够了。我建议选:下单的事务方法、Mapper里的动态SQL查询。这两段代码量不大,但每一行都能说出设计理由,答辩时你有的可讲。不用把整个系统几百行代码全贴进去,老师看到大段代码只会觉得你没用心整理。
6.2 演示路径设计:一条主线讲十五分钟
答辩当天现场演示时,不要东点一下西点一下。按一条业务主线来走:注册新用户→登录→查询从某地到某地的班次→选择班次下单→进入“我的订单”看到订单记录→切换到管理员账号→查看所有订单。这条链路覆盖了用户端和管理端两大模块,也展示了系统最核心的预定闭环。
演示前先准备好测试数据,比如提前往库里插入几条班次记录。不要在答辩现场临时新增班次,如果表单字段绑定有问题,当场暴露会非常尴尬。我当时帮B同学模拟答辩时,让他先启动项目、确认测试账号能登录、确认要查询的班次确实存在,这三件事做完了再进场,基本能平稳通过。
6.3 高频问题:答不上来的就照这个思路说
答辩高频问题通常集中在选型和设计上。那份系统的代码里,老师很容易问这几个问题,我建议你提前写好答案:
| 问题 | 回答要点 |
|---|---|
| 为什么要用SSM框架? | 各层职责分离,便于维护;Spring管理对象,SpringMVC处理请求,MyBatis操作数据库,三者各司其职 |
| 余票扣减怎么防止并发超卖? | 事务+条件UPDATE,库存字段作为条件参与更新,数据库行锁保证了原子性 |
| 用户密码是怎么存的? | 正规做法是MD5加盐或BCrypt哈希,如果这套系统使用的是明文,答辩时主动说你的改进方案 |
| 订单金额和车次价格为什么分开存? | 冗余字段,避免车辆改价后订单历史金额跟着变 |
| 这个系统还有什么可以改进的? | 引入Redis缓存热门班次、用消息队列处理下单请求、前后端分离 |
最后一个问题几乎是必问的,你至少要准备一个改进方向,不用多,一个就够。说的时候往真实需求上靠,比如“目前的查询每次都要访问数据库,如果并发高了,可以把热门班次缓存到Redis里”,比“我觉得这个系统已经很完善了”要加分得多。
做完这套课设后,我的一个习惯是:每次拿到新项目,第一件事翻事务配置,第二件事翻拦截器路径,第三件事翻数据库脚本的字符集。这三处是SSM项目最常出状况的地方,先确认它们没问题,再去看业务代码,能省下大量排错时间。你可以把这篇里的避坑清单存下来,做课设时逐个比对。希望帮到你。
本文还有配套的精品资源,点击获取