每年毕业设计季,后台总有一批人拿着同一个题目来问:基于SSM的旅客行李管理系统。这个题看起来像是最普通的信息管理系统,但真做起来,很多人会卡在同一个地方——不是代码写不出来,而是不知道业务该怎么做、状态怎么流转、表怎么设计。我过去几年帮不少学弟学妹审过类似项目,也完整带过两个毕设小组做过行李管理方向的系统,对这套SSM框架教程级别的开发流程还算熟。今天干脆把这套从选题分析到答辩准备的完整过程写出来,尽量少讲空话,多讲能直接用的东西。
下面这些内容,适合已经拿到这个题目的毕业生,也适合想用SSM做一套带“状态跟踪”性质的管理系统、但还没有完整设计思路的同学。行李管理系统本质上是一条典型的“单证流+状态流”业务,搞清楚它的数据模型和状态变更逻辑,再回头看Spring、SpringMVC、MyBatis三者的分工,你会觉得这套经典框架用在这里其实非常顺。
1. 拿到题目之后,先别急着建工程,把业务定义清楚
1.1 为什么同样是增删改查,这个题更容易被问倒
很多毕设管理系统,比如班级管理、图书管理,核心就是一张主表加几张关联表,业务上基本是线性操作:新增一条、查一个列表、改一下详情、删一条记录。做完这些,再凑个登录、导出Excel,一个系统就成型了。但“旅客行李管理”不一样,它蹭着航空物流的边,天然带着一个“状态变化”的问题。
如果你不管状态,把行李表做成只有“登记了”和“被领取了”两个状态,答辩老师随便问一句“一件行李从值机托运到最终提取,中间经历了哪些环节?你的系统怎么记录这个过程?”你会发现自己很难回答。因为业务上,行李在值机柜台称重、贴牌、过安检、分拣、装机、卸机、上转盘、被旅客提取,每个环节都是一个节点,每个节点都可能发生异常,比如安检不合格退回、错运到别的航班、到达后没人领取。
所以做这个题目,第一步不是打开IDEA建Spring项目,而是把业务边界想清楚。你是做机场行李全流程追踪,还是做简化版的“柜台寄存/提取管理系统”?如果是后者,那状态字段可以简单一些,桌面模式是“寄存中→已提取”;但如果你连标题都说旅客行李管理,通常老师默认你至少能覆盖托运到提取的流程。我建议在论文和系统里采用“值机托运—分拣—装机—到达—提取”这条主线,再补一个异常状态,这样既不算过度设计,又足够支撑起一篇毕设论文的业务分析章节。
1.2 SSM三件套在这个项目里的真实分工
很多同学能搭出SSM框架,但对三者的边界只停留在“Spring管对象、SpringMVC管请求、MyBatis管数据库”这句话上。到了这个项目里,你要能举出具体例子。
Spring最核心的贡献是IoC容器和AOP事务。行李登记时要同时写行李主表和行李状态轨迹表,如果第二张表插入失败,第一张表也得回滚,这个能力来自Spring的声明式事务,靠@Transactional完成,而这背后的核心是AOP动态代理。SpringMVC负责把浏览器的URL路由到Controller方法,比如/baggage/checkIn、/baggage/trace/list,同时负责参数绑定和JSON返回。MyBatis则负责把Java对象和数据库表映射起来,尤其适合这种SQL条件多变的管理系统,动态SQL写起来非常直观。
理解到这个层面之后,你在论文的“关键技术介绍”部分就不会只是抄书上那段框架概念了,而是能写清楚“本系统如何使用Spring管理Service对象、如何通过SpringMVC接收行李查询请求、如何利用MyBatis动态SQL完成多条件检索”,这一步就能拉开和普通模板化论文的差距。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 画一张业务地图:行李在系统里到底走哪些节点
2.1 一个最简单的全流程闭环怎么设计
做业务设计时,我喜欢先把“状态/动作/角色”三个要素列出来。这个系统里,行李对象至少要有五六个状态,每个状态要对应一个操作动作和操作人。
我建议这样设置状态编码,方便数据库存储和前端展示:
| 状态编码 | 状态名称 | 触发动作 | 典型角色 |
|---|---|---|---|
| 0 | 已收运 | 值机柜台托运行李,生成行李牌 | 值机员 |
| 1 | 安检中 | 行李进入安检通道 | 安检员 |
| 2 | 已分拣 | 安检通过,按航班分拣 | 分拣员 |
| 3 | 已装机 | 行李装上飞机货舱 | 装载员 |
| 4 | 已到达 | 航班到达,行李进入提取转盘 | 到达管理员 |
| 5 | 已提取 | 旅客凭凭证完成核验提取 | 行李查询员 |
| 6 | 异常 | 安检退回/错运/破损/无人认领 | 系统标记 |
如果你觉得六七个状态对毕设来说偏多,可以砍到四个核心状态:0-已收运、1-运输中、2-已到达、3-已提取,加上一个99-异常。砍掉状态没问题,但要在需求分析里能自圆其说。我最怕的是学生系统里有两个状态,一个是“未领取”,一个是“已领取”,论文里却写“本系统实现了行李全流程追踪”,这是明显的逻辑漏洞,答辩老师一眼就能看出来。
2.2 角色权限和功能模块不要贪多
旅客行李管理系统不需要像真正的机场系统那样对接离港系统、安检系统,也不需要做自助行李托运设备。毕设的功能模块控制在六个左右就够了:系统登录与用户管理、旅客信息管理、行李登记管理、行李状态跟踪、行李查询统计、基础数据管理(航班和柜台信息)。
用户角色建议拆成三类:系统管理员、值机员、行李查询员。管理员维护用户、航班等基础数据;值机员负责登记行李、办理状态流转;行李查询员则负责查询、处理异常、办理提取。这里可以顺势用上SpringMVC的拦截器做权限控制,按角色名判断是否放行请求,既实现了功能,又能在论文里写“系统采用拦截器实现访问控制”,比只做一个登录功能要有说服力得多。
2.3 异常分支是体现系统完整度的地方
每个系统都做得通主流程,真正体现水平的是异常分支。行李管理里常见的异常场景有三个:
第一个是安检未通过,行李需要从“安检中”退回到值机柜台,这时候不是简单把状态改成0,而是要在状态追踪表里追加一条“安检退回,原因:疑似超规物品”的记录。第二个是错运,行李被装上别的航班,系统需要有一条“改签航班”的记录,关联到新的航班号。第三个是无人认领,到达后超过一定时间未提取,系统自动或人工把状态置为“异常”,并记录滞留天数。
这些异常分支不需要写得多复杂,但要在数据库表里留出remark、operator_id、operation_time这些字段,并在代码里设计好状态变更的接口。当你做到这一步,你的系统就不再是普通课设,而是有业务闭环的毕设项目了。
3. 数据库建模:这一步的质量直接决定后期写代码的速度
3.1 四张核心表和一个追踪表
我见过很多学生一上来就建一张超级大表,把所有字段塞进去,比如行李表里既放旅客姓名身份证号,又放航班号目的地,还有当前状态、追踪记录。这种设计后面改需求时非常痛苦。正确的做法是拆表,让每张表的职责清晰。
本项目建议至少建这几张表:
user:系统用户表,字段有id、username、password、real_name、role、create_time。passenger:旅客表,字段有id、passenger_name、id_card、phone。flight:航班表,字段有id、flight_no、departure_city、arrival_city、departure_time、arrival_time。baggage:行李主表,核心字段有id、baggage_tag(行李牌编号)、passenger_id、flight_id、weight、baggage_type、status、check_in_time、update_time。baggage_trace:行李状态追踪表,字段有id、baggage_id、from_status、to_status、operator_id、operation_time、remark。
其中baggage_trace是这个系统里最有价值的一张表。它不存行李的当前状态,而是记录每一次状态变化的历史轨迹。页面上的“行李状态时间线”就是查这张表渲染出来的。很多学生以为行李表里加个状态字段就够了,等被问到“你如何证明一件行李在某时某刻被某个人操作过”时,就答不上来。有了追踪表,这个问题自然就解决了。
3.2 行李牌编号怎么生成
行李牌编号是一个很容易被忽视的细节。正常做法不是用数据库自增id直接当行李牌号,而是生成一个业务上可读的唯一编码。我常用的规则是:
TAG + yyyyMMddHHmmss + 三位随机数
比如TAG20250514093045012。这样生成的好处是:哪怕数据库里id被别人猜到,也不能通过遍历id批量查询到所有行李信息,同时业务上从编码本身就能看出收运时间。
这个编号一定要加唯一索引,因为它是行李在系统内外流通的凭证。值机员打印出来的标签上就是这个编号,后续所有查询都靠框扫这个编号。
3.3 字段类型、索引和逻辑删除的取舍
有几个字段类型的选择可能会影响后面的开发体验。第一是weight,建议用DECIMAL(5,2),不要用FLOAT,避免浮点精度问题。第二是所有时间字段用DATETIME,在Java里对应LocalDateTime,配合Jackson设置好格式就能直接返回给前端。第三是phone和id_card这类字段,如果只是存不参与计算,建议直接VARCHAR(20)、VARCHAR(30),不要用BIGINT,因为身份证号超过BIGINT范围或者带X就麻烦了。
还有个思路建议你早点想清楚:删除操作能不能做?业务上,已经登记过的行李不应该被物理删除,比如录错了航班号,应该走“修改”而不是“删除”。如果需求里确实需要删除权限,建议用逻辑删除,给表加一个is_deleted字段,查询时默认过滤。这样既保留了数据完整性,又能在答辩时讲清楚为什么不用物理外键级联删除。
关于外键,我不建议在数据库层面加太多物理外键约束,尤其是baggage_trace关联baggage这种日志型表。程序里控制好逻辑关系就够了,数据库物理外键在后期插入大量测试数据、批量修改时非常碍事。可以给baggage_tag、flight_no、passenger_id_card等高频查询字段建索引,这才是真正能帮到性能的做法。
4. SSM工程结构与三层代码:不是把类建出来就叫分层
4.1 一个不容易被扣分的Maven目录结构
很多同学建项目时习惯把Controller、Service、Dao全写在com.example一个包里,这样功能上能跑,但结构上缺少章法。建议按业务分包和分层结合的方式组织,参考下面这种:
text复制com.campus.airport
├── common // 统一返回结果、全局异常、常量类
├── config // 配置类(如果有JavaConfig)
├── controller // 控制层
├── interceptor // 登录/权限拦截器
├── mapper // MyBatis接口
├── model // 实体类
├── service // 业务层接口
│ └── impl // 业务层实现
└── vo // 视图对象,比如查询条件封装
common包里放一个统一返回类很关键,比如Result<T>,包含code、message、data三个字段。Controller每个方法都返回这个对象,前端Ajax收到后直接判断code是不是200,不用为每个接口单独写一套成功失败逻辑。这个类本身代码量不大,但在答辩讲解系统设计时能体现工程意识。
4.2 Spring与SpringMVC配置的常见卡点
SSM传统做法是XML加注解混合配置。这里我不打算给你贴一份没有上下文的长配置文件,只强调几个必坑点。
第一,web.xml里要给DispatcherServlet设置load-on-startup,并加载Spring容器配置文件。如果你漏配了Spring的ContextLoaderListener,Service层对象根本不会被Spring管理,Controller里自动注入会直接报空指针。
第二,Spring容器和SpringMVC容器要分开扫描。我习惯在Spring的配置文件里扫描service、mapper、common等组件,在SpringMVC的配置文件里只扫描controller包。如果两个容器同时扫描service,可能导致事务代理失效——因为你配的@Transactional是加在Spring容器管理的Service对象上,而Controller里注入的可能是另一个容器里的对象。
第三,静态资源一定要放行。如果页面里引用了CSS、JS、图片,但你没在SpringMVC配置里放行静态资源,浏览器控制台会报一堆404,页面样式全丢。传统做法是加<mvc:resources mapping="/static/**" location="/static/"/>,或者用<mvc:default-servlet-handler/>。
第四,拦截器配好之后,要记得放行登录页、登录接口,不然会死在登录这个环节上。我自己调试时最常见的问题是改了拦截器,越权访问测试时发现连静态资源和登录页都被拦了,排查半天发现是拦截路径写成了/*,应该写成/**,但静态资源路径又没排除。
4.3 MyBatis接口与XML,什么时候用动态SQL
MyBatis有两种写法:接口注解和XML映射。小项目里用注解确实快,但一旦SQL复杂,注解会让你在Java代码里拼出一大段字符串,可读性很差。我建议本项目的所有查询都在XML里写,尤其是多条件组合查询。
比如行李查询页,可能有行李牌编号、旅客姓名、状态、航班号、登记时间段五个查询条件,而且用户可能只填其中两三个。这种情况用注解拼@Select会写一堆<script>标签,而XML里直接这样写:
xml复制<select id="selectBaggageList" resultType="com.campus.airport.model.Baggage">
SELECT b.*, p.passenger_name, f.flight_no
FROM baggage b
LEFT JOIN passenger p ON b.passenger_id = p.id
LEFT JOIN flight f ON b.flight_id = f.id
<where>
<if test="baggageTag != null and baggageTag != ''">
AND b.baggage_tag = #{baggageTag}
</if>
<if test="status != null">
AND b.status = #{status}
</if>
<if test="passengerName != null and passengerName != ''">
AND p.passenger_name LIKE CONCAT('%', #{passengerName}, '%')
</if>
<if test="flightNo != null and flightNo != ''">
AND f.flight_no = #{flightNo}
</if>
</where>
ORDER BY b.check_in_time DESC
</select>
这里要注意,<where>标签能帮你去掉多余的AND,但它不会自动去掉只有单条件时条件前面的AND,你写SQL时要习惯在各行条件前写AND,这是MyBatis官方推荐风格。另一个容易踩的细节是模糊查询的写法,不要写成#{passengerName}拼在LIKE里,而是用CONCAT('%', #{passengerName}, '%'),避免SQL拼接和传参问题。
5. 核心业务代码拆解:登记、查询、提取三板斧
5.1 行李登记:一个事务里要完成两件事
行李登记是整个系统的入口,也是业务上最需要保证原子性的操作。一次登记不只插入一条行李主记录,还要生成行李牌编号、插入一条状态轨迹。如果只插了主记录,没插轨迹,系统里就查不到“这件行李是何时被收运”的信息。
Service层实现我一般这样写:
java复制@Override
@Transactional(rollbackFor = Exception.class)
public Baggage checkIn(BaggageCheckInVO vo) {
Baggage baggage = new Baggage();
baggage.setBaggageTag(generateTag(vo.getFlightId()));
baggage.setPassengerId(vo.getPassengerId());
baggage.setFlightId(vo.getFlightId());
baggage.setWeight(vo.getWeight());
baggage.setStatus(0); // 已收运
baggageMapper.insert(baggage);
baggageTraceMapper.insert(new BaggageTrace(
baggage.getId(), null, 0,
UserContext.getUserId(),
"值机柜台收运,生成行李牌",
LocalDateTime.now()
));
return baggage;
}
几个关键点都放在这里了。
@Transactional 的rollbackFor为什么指定成Exception.class?因为Spring默认只在遇到RuntimeException回滚,如果你在业务代码里抛了一个普通异常但没有指定回滚规则,事务会神奇地不生效,数据库里会出现主表有数据、轨迹表没数据的脏情况。
generateTag是自己写的工具方法,生成前面说的唯一业务编码。注意这个操作不应该放在Controller,因为它是业务规则,放在Controller里会破坏分层。
UserContext是我用来保存当前登录用户ThreadLocal的工具类,登录拦截器里把用户信息放进去,业务代码里可以直接拿操作人。这样每条轨迹都能记录“谁做的”,比在每个方法参数里来回传userId要清爽。
5.2 状态变更统一走一个Service方法
如果业务代码里到处出现baggage.setStatus(2),以后再改状态判断逻辑时就会特别痛苦。我强烈建议把状态流转收敛到一个方法里,比如:
java复制@Override
@Transactional(rollbackFor = Exception.class)
public boolean changeStatus(Long baggageId, Integer fromStatus,
Integer toStatus, Integer operatorId, String remark) {
int count = baggageMapper.compareAndSetStatus(baggageId, fromStatus, toStatus);
if (count == 0) {
throw new BusinessException("状态已变更,请刷新后再操作");
}
baggageTraceMapper.insert(new BaggageTrace(
baggageId, fromStatus, toStatus, operatorId, remark, LocalDateTime.now()
));
return true;
}
这里用了一个很小的并发处理技巧:compareAndSetStatus是MyBatis里的一个update语句,通过WHERE id = ? AND status = ?来防止两个操作同时把同一件行李状态改掉。第一条更新成功会对行加锁,后一条更新匹配不到记录,返回0,于是我们知道操作冲突了。这个设计叫乐观锁思路,简单有效,不用引入重量级锁也能应对毕设场景。
状态变更统一走这个方法的另一个好处是,以后要加“是否允许从某个状态跳到某个状态”的校验,只需要在这一个方法里加规则就行。否则散落的update会让系统越改越乱。
5.3 提取行李:不仅要查状态,还要核验凭证
到了行李提取环节,系统不能只根据行李id把状态改成“已提取”。实际业务里提取人可能是旅客本人,也可能是代领人,核验流程至少要有两个信息:行李牌编号和旅客身份证号。所以提取接口的参数要包含baggageTag和idCard,Controller先从页面拿到这两个值,调Service去匹配。
Service里的逻辑大致是:
- 根据
baggageTag查到行李主记录,如果查不到,提示“行李牌编号不存在”。 - 判断行李状态是否等于“已到达”,不是的话提示“该行李尚未到达,不能提取”。
- 用行李里的
passengerId查旅客,比对身份证号是否一致,不一致提示“证件核验失败”。 - 全部通过后,调用统一的
statusChange方法,把状态从4改成5,轨迹备注记录操作员。
这四步每一步其实都在回答一个“为什么系统要这么设计”的问题。答辩时老师问到“如果找别人代领怎么办”,你至少还能补一句“目前系统支持本人证件核验,如果要扩展代领,可以在表中增加代领人姓名和证件号字段,并对提取接口增加代领人信息校验”。能看到可扩展性,比愣在那里强很多。
6. 页面实现与联调:JSP、AJAX与状态回显
6.1 选JSP还是前后端分离
SSM是传统Servlet技术栈,默认配套的页面方案是JSP。很多同学纠结要不要把Vue和SpringBoot弄进来做一个“前后端分离”的增强版。我的建议是:除非你非常熟练Vue、Webpack、代理转发、跨域处理,否则毕设项目最好还是扎根在JSP加前端组件上,比如Bootstrap、Layui、AdminLTE这类开源管理模板。
这不是技术保守,而是可控性优先。JSP里写${baggage.baggageTag}、${statusMap[baggage.status]}确实简单直观,出现问题也容易排查。前后端分离意味着你要处理跨域请求、静态资源独立部署、接口联调,对于以展示框架能力和数据库设计为核心的毕设项目,这些工作会分走大量时间,而且并不加分。
当然,如果你希望某些页面效果更现代,可以在JSP页面里引入Vue的CDN版本,只在单个页面内部进行数据绑定,不搞工程化构建。比如行李状态时间线页面,用Vue把后端返回的轨迹数组渲染成时间节点,效果很好,代码量也不大。这种做法不需要webpack,也不需要node环境,属于一个折中方案。
6.2 三个核心页面:登记表单、查询列表、状态时间线
行李登记页是比较典型的“表单页”,核心要处理好联动。旅客和航班不能只靠手输id,页面里应有下拉选择或模糊搜索框。航班下拉如果数据量大,可以使用Select2这类组件,支持输入关键词过滤。这个页面必备的信息有:旅客姓名、身份证号、手机号、航班号、行李重量、行李类型(托运/超规/宠物)。提交时通过$.ajax把表单数据转成JSON提交到/baggage/checkIn,成功后再刷新表格。
行李查询列表页是系统的门面,建议支持按行李牌编号、状态、航班号、时间段组合筛选。这里最大的提醒是:不要用原生<form>整页刷新提交查询。页面会闪一下,搜索条件还得靠URL参数回显,很麻烦。我习惯用表单序列化提交Ajax到后端,后端返回HTML片段或者JSON数据,前端局部刷新tbody。这里可以提前约定好统一返回结构,前端判断code逻辑,不需要每个方法重新写成功失败分支。
状态时间线页是最能展示系统特色的页面。页面输入一个行李牌编号,后端返回两个数据:行李基础信息(当前状态、重量、航班)和轨迹列表(时间、操作人、结果描述)。前端可以把轨迹按时间正序排成一条垂直线,每个节点显示状态名称和时间。这类页面做出来之后,往演示视频里一放,答辩观感立刻就不一样了,因为这直接对应“追踪”两个字。
6.3 前后端联调的几个数据格式坑
第一,时间格式。Java 8的LocalDateTime默认序列化出来是一串数组格式,比如[2025, 5, 14, 9, 30, 0],前端没法直接用。解决方法是配置Jackson的JavaTimeModule,或者在项目里写一个全局配置:
java复制@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.modules(new JavaTimeModule())
.build();
converter.setObjectMapper(objectMapper);
converters.add(0, converter);
}
第二,Ajax提交表单数据。如果你提交的是JSON字符串,后端Controller参数要加@RequestBody;如果是普通表单序列化serialize(),则不需要加@RequestBody,直接用对象接收就行。这两种方式经常被混在一起,出错时不好排查。我建议全项目统一用JSON方式,后端接口加@RequestBody,风格一致。
第三,JSP里EL表达式取不到值。如果设置了${pageContext.request.contextPath}作为资源前缀,要确认页面顶部的<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>已经引入,JSTL没引全时很多标签会直接原样输出或不生效。另一个容易踩的坑是SpringMVC返回视图路径时,如果把返回字符串写成了forward:/baggage/list,会导致本页URL变成列表路径但页面内容还是旧页面的情况,排查时特别绕。
7. 从开发到答辩:我踩过的坑和临场建议
7.1 环境与部署类问题
SSM项目在本地跑通,换一台机器或换到实验室电脑就各种报错,这种事太常见了。先检查三个硬版本:JDK版本、Tomcat版本、Maven仓库里依赖的兼容性。如果你用的是JDK8,Spring建议用5.0到5.2之间的版本,不要一上来配Spring 6和Jakarta包名,因为Spring 6要求JDK17,而且javax.servlet全部变成了jakarta.servlet,SSM老项目里大量代码会直接报找不到包。
数据库连不上是第二高频问题。用MySQL 8以上的数据库,驱动类要写成com.mysql.cj.jdbc.Driver,URL要带serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8,否则要么报时区错,要么报SSL警告,要么连上之后中文乱码。
Tomcat控制台中文乱码问题也很常见。如果代码里请求和响应都加了CharacterEncodingFilter,且数据库连接参数里指定了characterEncoding=utf8,前端页面也设置了Content-Type里的charset,那么大概率是Tomcat的日志配置或服务端到浏览器响应时缺了编码设置。可以在server.xml的Connector上增加URIEncoding="UTF-8",这是GET请求参数中文乱码最普遍的一个解决点。
7.2 演示数据要准备得像真实业务
很多同学把系统跑起来就直接去答辩,打开页面里面全是“张三1、张三2、测试行李123,状态=1”,这种演示效果很减分。建议花半小时造一套“能讲出故事”的数据。
举个我常用的数据例子:旅客李然,身份证号用真实规则造一个,但别用真实存在的人的完整信息,手机号也注意不要填真实号码。她乘坐的航班是MU5121,从北京首都飞上海虹桥,行李牌编号TAG20250514093045012,重量23.5kg。要为这个行李故意构造完整的五条轨迹记录:14号09:30值机收运,09:45安检通过,10:05分拣完成,航班10:40起飞,当天12:30到达上海,状态流转到“已到达”。如果做提取演示,再操作一次提取,状态变“已提取”。
演示的关键是数据之间自带逻辑:同一个航班下有若干行李,到达时间一致,部分已提取,部分还在转盘待提取,甚至有一件显示“异常—无人认领”。这样讲解时就能形成一条完整的故事线,而不是零散地演示每个功能按钮。
7.3 高频答辩问题怎么接住
把SSM背得再熟,不如准备几个具体的业务问题。老师大概率会问四类:框架原理类、数据库设计类、事务安全类、业务扩展类。
框架原理类常见问题包括“Spring的IoC是怎么实现的”“SpringMVC一次请求的完整流程是什么”“MyBatis和JDBC的关系”。回答时一定要结合项目:IoC就是把UserService、BaggageMapper这些对象的创建和维护交给Spring容器,要的时候注入;SpringMVC流程是前端发请求给DispatcherServlet,它通过HandlerMapping找到Controller方法,执行完通过ViewResolver解析到JSP页面;MyBatis底层本质是对JDBC的封装,把SQL结果集自动映射到实体对象。
数据库设计类通常会问“为什么用逻辑删除”“表结构里为什么不用外键”。你如实回答自己为了数据完整性用逻辑删除、为了避免性能问题不设物理外键,并补充说明是在service层控制关联关系,这是站得住的理由,比含糊其辞好很多。
事务安全类最常问的是“如果同时有两个窗口都想提取同一件行李,会不会出问题”。这个问题就是命中我们前面用乐观锁更新状态、影响行数为0就抛异常的地方,答起来了非常自然。你甚至可以补充:虽然这个场景在实践中发生的概率没那么高,但状态的一致性是业务正确性的底线,所以我在状态变更SQL里加了AND status = ?来判断。
7.4 最后一点个人心得
我自己做这类系统最大的体会是:整套SSM代码写出来并不难,难的是你心里有没有一条完整的业务主线。如果你能从“旅客托运一件行李”这个动作开始,一路讲到“到达后提取核销”,中间每一步都有数据库表记录、有状态流转约束、有页面操作支撑,那这个项目的完成度已经超过相当一部分毕设了。真正写代码时,记得把时间多花在数据库设计上,主线理顺了,后面只是执行问题。
希望你做这个题目的过程别只是复制粘贴代码。试着在登记行李的时候想想事务为什么能回滚,在状态时间线页思考每条记录来自哪张表,在提取接口遇到状态不对时想想校验顺序为什么这样放——把这些想明白,你答辩时的底气会完全不一样。
