☰
JavaEE小型酒店管理系统毕业设计:房态、预订与退房结算
2026/10/29 1:49:15 网站建设 项目流程

简介:面向JavaEE课程设计与毕业设计场景的小型酒店管理系统源码包,适合具备Java Web基础、希望完整实践SSH(Spring+Struts+Hibernate)分层开发的学生与初级开发者,可借此理解酒店业务从请求处理、事务管理到数据库映射的落地方式。压缩包共262个文件、约18.69MB,以49个java源码、25个jsp页面、24个xml配置、54个class及54个jar依赖为主,另含properties、样式脚本和说明文档,覆盖源码、依赖与页面资源。目前已有1695人学习下载。系统涵盖用户权限、房间状态与价格、预订入住退房、账单收费、经营报表及参数设置等模块,Controller调用Service、Hibernate完成实体映射,JSP结合EL/JSTL展示数据;读者可据此梳理SSH项目的包结构、配置方式与事务边界,并参考业务表设计和测试类组织,形成可复用的毕业设计实现思路与排错参照。

1. 小型酒店管理系统的需求切口:为什么先做房态而不是先做订房

小型酒店管理系统毕业设计最容易翻车的地方,不是技术栈不够新,而是功能铺得太大。很多同学一上来就想做在线预订、会员积分、报表大屏,结果做到答辩前一周,前台最核心的“看房态、办入住、退房结算”还没跑通。我在带 JavaEE 毕业设计时一般会建议先把需求收窄:系统服务对象是小型酒店的前台和店长,日客房量在几十间以内,核心诉求是房态准确、入住可追溯、账务能对上。功能范围控制在房态管理、预订、入住退房、账务流水和权限五个模块,技术栈用 JavaEE 常见的 Spring MVC + MyBatis + JSP + MySQL,部署在 Tomcat 上,一台普通笔记本就能演示。这套方案适合需要交毕业设计或课程设计的学生,也适合刚接手小型酒店类项目的开发者做参考。先把房态这个“地基”做稳,后面的预订和入住才有意义。

2. 技术选型与总体架构:JavaEE 分层落地和四个模块的职责边界

2.1 技术栈取舍:Spring MVC + MyBatis + JSP 的毕业设计合理性

小型酒店管理系统毕业设计通常只有 8 到 12 周,一个人或两三个人完成,技术选型的首要目标是“能跑通、能讲清、能演示”。JavaEE 体系里,Spring MVC + MyBatis + JSP + MySQL + Tomcat 是比较稳的组合:请求从 Controller 进入,Service 管业务和事务,Mapper 管 SQL,JSP 负责页面渲染,答辩时打开浏览器就能演示,不依赖额外的前端构建环境。微服务、前后端分离不是不能做,但时间会耗在接口联调、跨域和部署上,最后业务逻辑反而讲不深。我一般会把版本统一锁死,避免“在我电脑上能跑”的玄学问题。

<!-- pom.xml 关键依赖,版本按实验室或学校环境统一锁定 --> <dependencies> <!-- Spring MVC:处理 Web 请求和视图跳转 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.39</version> </dependency> <!-- MyBatis 与 Spring 整合,负责 SQL 映射 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.2</version> </dependency> <!-- MySQL 驱动,版本与本地数据库保持一致 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- Servlet API 由 Tomcat 提供,scope 设为 provided 避免冲突 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies>

这段配置的重点不在版本号,而在依赖边界。javax.servlet-api用provided,表示编译时需要、运行时由 Tomcat 容器提供,打包成 war 后不会和容器自带的 Servlet 包打架。MySQL 驱动版本要和本机数据库匹配,否则会出现连接失败但报错信息很模糊的情况。打包方式在pom.xml里设为war,把 Tomcat 作为外部容器,答辩时换一台机器也能部署。数据库连接信息不要写死在 Java 代码里,放到jdbc.properties,演示前只改一个文件就能切换环境。

2.2 三层架构与包结构:从 controller 到 mapper 的调用链

小型系统不需要过度分层,但 Controller、Service、Mapper 三层必须清晰。Controller 只做参数接收、简单校验和视图跳转,不写业务判断;Service 承担业务规则和事务边界,比如“预订占房”必须在一个事务里完成房态更新和订单写入;Mapper 只负责 SQL,不写“如果状态等于几就怎样”的逻辑。包结构按职责划分,避免所有类堆在一个包下。

src/main/java/com/example/hotel ├── controller # 接收页面请求,返回 JSP 视图或 JSON ├── service # 业务规则、事务边界、跨模块编排 │ └── impl ├── mapper # MyBatis 接口,只声明数据访问方法 ├── entity # 与数据库表对应的持久化对象 ├── dto # 页面入参和出参,避免实体直接暴露给页面 └── common # 统一异常、拦截器、工具类 src/main/resources ├── mapper # MyBatis XML,写 SQL 和结果映射 ├── spring # Spring 容器与 MVC 配置 └── db # schema.sql、data.sql 初始化脚本

以一个“查询房态列表”的请求为例,调用链是RoomController.list()→RoomService.listRooms()→RoomMapper.selectRoomList()→room表。Controller 不直接调 Mapper,否则事务和权限校验会散落在各处。DTO 和 Entity 分开的好处是页面字段可以裁剪,比如入住登记页面不需要把数据库里的version字段暴露出去。统一异常放在common里,Service 抛出业务异常,Controller 统一捕获后跳转到错误页,避免每个方法都写一遍 try-catch。这套结构不复杂,但答辩时能清楚回答“请求怎么走、事务在哪一层、SQL 在哪定义”。

2.3 模块边界:房态、预订、入住退房、账务与权限的拆分

模块拆分的原则是“一个模块只对自己核心表负责”。房态模块管房间档案和状态变更,预订模块管预订单,入住退房模块管在住记录,账务模块管流水,权限模块管用户和角色。跨模块操作通过 Service 调用完成,不直接跨层访问对方的 Mapper。下面这张表可以作为毕业设计开题时的模块说明。

模块核心职责关键表典型入口
房态模块房间档案、可售查询、维修锁房、状态变更room、room_status_log/room/list、/room/status
预订模块预订单创建、取消、到店确认booking/booking/create
入住退房入住登记、换房、退房、在住查询checkin/checkin/save、/checkout/settle
账务模块押金、消费、退款、结算流水account_flow/account/flow
权限模块登录、角色、菜单、操作日志sys_user、sys_role、operation_log/login、/user/list

模块边界清楚之后,后面加功能不容易乱。比如“换房”看起来是房态模块的事,实际涉及入住记录和两个房间的状态,应该放在入住退房模块的 Service 里,由它调用房态模块的更新方法。账务模块不直接改房态,退房时由入住退房模块先结算、再释放房间。权限模块用拦截器做后端校验,不能只在 JSP 里隐藏按钮。小型酒店管理系统毕业设计能把这几条边界讲清楚,架构部分基本就稳了。

3. 数据库与核心表设计:房态、订单、入住记录怎么落表

3.1 房间与房态表:把“可售/在住/维修”拆成状态机

房态表最容易犯的错,是只在一个room表里放一个status字段,然后所有业务都直接改它。这样短期能跑,但一旦出现“前台显示空房、客房说有客人”的情况,就查不出是谁改的。通常的做法是保留房间主表的状态字段作为当前快照,同时增加一张状态变更流水表,每次变更都记一条。状态值不要用魔法数字散落在代码里,统一约定:0 可售、1 在住、2 维修、3 预订。

CREATE TABLE room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(16) NOT NULL COMMENT '房间号,如 301', room_type VARCHAR(32) NOT NULL COMMENT '房型,如大床房、标间', price DECIMAL(10,2) NOT NULL COMMENT '门市价,金额用 DECIMAL', status TINYINT NOT NULL DEFAULT 0 COMMENT '0可售 1在住 2维修 3预订', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', UNIQUE KEY uk_room_no (room_no) ) COMMENT='房间档案与当前房态快照'; CREATE TABLE room_status_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_id BIGINT NOT NULL, from_status TINYINT NOT NULL, to_status TINYINT NOT NULL, biz_type VARCHAR(32) NOT NULL COMMENT 'BOOKING/CHECKIN/CHECKOUT/REPAIR', biz_id BIGINT DEFAULT NULL COMMENT '关联业务主键', operator_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_room_time (room_id, created_at) ) COMMENT='房态变更流水,用于对账和排查';

room.status是当前快照,room_status_log是黑匣子。每次房态变更,Service 里先更新room,再插入一条日志,两个动作放在同一个事务里。version字段用于乐观锁,防止两个前台同时把同一间房改成不同状态。biz_type和biz_id记录这次变更来自哪张业务单,比如入住登记对应CHECKIN和checkin.id,退房对应CHECKOUT。答辩时如果被问“怎么保证房态可追溯”,把这张流水表拿出来,比口头解释更有说服力。

3.2 预订与入住表:从预订单到在住记录的字段与关联

预订单和在住记录要分开。预订单表示“客人计划来住”,在住记录表示“客人已经入住”。两者用booking_id关联,散客没有预订单时该字段可以为空。预订表需要保留入住日期和离店日期,方便查询某一天的可售房;入住表需要保留实际入住时间、押金和在住状态。金额字段统一用DECIMAL(10,2),不要用FLOAT或DOUBLE,否则退房结算时容易出现几分钱的误差。

CREATE TABLE booking ( id BIGINT PRIMARY KEY AUTO_INCREMENT, booking_no VARCHAR(32) NOT NULL COMMENT '预订单号,业务唯一', room_id BIGINT NOT NULL, guest_name VARCHAR(64) NOT NULL, guest_phone VARCHAR(32) NOT NULL, check_in_date DATE NOT NULL COMMENT '计划入住日期', check_out_date DATE NOT NULL COMMENT '计划离店日期', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待入住 1已入住 2已取消 3已退房', total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_booking_no (booking_no), KEY idx_room_date (room_id, check_in_date, check_out_date) ) COMMENT='预订单'; CREATE TABLE checkin ( id BIGINT PRIMARY KEY AUTO_INCREMENT, booking_id BIGINT DEFAULT NULL COMMENT '散客入住时为空', room_id BIGINT NOT NULL, guest_name VARCHAR(64) NOT NULL, guest_id_no VARCHAR(32) DEFAULT NULL COMMENT '证件号,演示数据脱敏', check_in_time DATETIME NOT NULL, check_out_time DATETIME DEFAULT NULL COMMENT '为空表示仍在住', deposit DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '押金', status TINYINT NOT NULL DEFAULT 0 COMMENT '0在住 1已退房', KEY idx_room_status (room_id, status) ) COMMENT='入住记录';

预订表的状态流转要固定:创建后为 0,办理入住后改成 1,取消改成 2,退房改成 3。入住表的check_out_time为空表示在住,这个判断比额外加一个字段更直接。idx_room_date索引用于按房间和日期区间查询,但要注意日期区间重叠的 SQL 写法,常见条件是check_in_date < 离店日期 AND check_out_date > 入住日期,不要写成简单的BETWEEN,否则边界日期会算错。这些小地方在答辩时很容易被追问。

3.3 账务流水与操作日志:毕业设计里最容易被忽略的两张表

很多毕业设计只做“退房时弹一个金额”,不落账务流水,最后金额对不上就查不出原因。账务表按流水记录,不做“余额字段反复更新”,每条记录表示一次资金动作:收押金、记消费、退款、结算。金额用正负号区分收入支出,汇总时直接SUM,简单可靠。操作日志表记录关键动作,比如谁改了房态、谁删了订单,答辩时能回答“这个操作是谁做的”。

CREATE TABLE account_flow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, checkin_id BIGINT NOT NULL, flow_type VARCHAR(32) NOT NULL COMMENT 'DEPOSIT/CONSUME/REFUND/SETTLE', amount DECIMAL(10,2) NOT NULL COMMENT '正数收入,负数支出', remark VARCHAR(255) DEFAULT NULL, operator_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_checkin (checkin_id) ) COMMENT='账务流水'; CREATE TABLE operation_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, module VARCHAR(32) NOT NULL COMMENT 'ROOM/BOOKING/CHECKIN/ACCOUNT', action VARCHAR(32) NOT NULL COMMENT 'CREATE/UPDATE/DELETE/SETTLE', target_id BIGINT DEFAULT NULL, detail VARCHAR(512) DEFAULT NULL COMMENT '变更前后摘要', ip VARCHAR(64) DEFAULT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_user_time (user_id, created_at) ) COMMENT='关键操作日志';

账务流水不要只存一个“最终金额”,要把押金、消费、退款分开记。退房时先汇总流水,再写一条SETTLE结算记录,最后更新入住状态和房态。操作日志不需要记所有查询,只记新增、修改、删除和结算这类关键动作。日志内容不要存完整证件号,演示数据也要脱敏,detail里存“房态从 3 改为 1”这种摘要就够。这两张表在论文里可能只占一小节,但它们是系统能不能自圆其说的关键。

4. 关键业务流程实现:从预订到退房的代码落点

4.1 房态查询与预订占房:库存扣减与并发校验

预订占房的核心不是“插一条订单”,而是“先确认房间在目标日期可用,再同时改房态和写订单”。小型系统不需要复杂的库存表,但必须防止两个请求同时把同一间房订出去。我一般用“状态条件更新 + 乐观锁版本号”做第一道防线,再用数据库唯一约束做第二道防线。下面这段 Service 代码把占房和写订单放在同一个事务里,任何一步失败都回滚。

@Service public class BookingService { @Autowired private RoomMapper roomMapper; @Autowired private BookingMapper bookingMapper; @Transactional(rollbackFor = Exception.class) public Long createBooking(BookingDTO dto) { // 1. 带版本号的条件更新:只有房态仍为“可售”且版本一致才占房成功 int updated = roomMapper.updateStatusForBooking( dto.getRoomId(), dto.getVersion(), 0, 3); if (updated == 0) { throw new BizException("房间已被占用,请刷新房态后重试"); } // 2. 写入预订单,booking_no 由日期、房间号和序列生成 Booking booking = new Booking(); booking.setBookingNo(generateNo(dto)); booking.setRoomId(dto.getRoomId()); booking.setGuestName(dto.getGuestName()); booking.setGuestPhone(dto.getGuestPhone()); booking.setCheckInDate(dto.getCheckInDate()); booking.setCheckOutDate(dto.getCheckOutDate()); booking.setStatus(0); booking.setTotalAmount(dto.getTotalAmount()); bookingMapper.insert(booking); return booking.getId(); } }

updateStatusForBooking对应的 Mapper SQL 必须把status和version都放进WHERE:UPDATE room SET status = #{toStatus}, version = version + 1 WHERE id = #{roomId} AND status = #{fromStatus} AND version = #{version}。参数含义分别是房间主键、页面带来的版本号、期望的原状态和目标状态。返回 0 表示有人先改了房态,当前请求不能继续。事务注解不能省,否则房态改了、订单没写,就会出现“房间被占但没有订单”的脏数据。预订成功后页面要刷新房态,不能只提示“预订成功”就完事。

4.2 入住登记与换房:入住记录写入和房态联动

入住登记有两种来源:预订转入住和散客直接入住。预订转入住时,先把预订单状态从“待入住”改成“已入住”,用条件更新防止同一个预订单被重复办理;然后再插入入住记录、更新房态、写操作日志。散客入住没有预订单,直接插入入住记录并把房态改成在住。换房则是在入住记录上改room_id,同时释放原房间、占用新房间,三个动作必须在同一个事务里。

@Service public class CheckinService { @Autowired private CheckinMapper checkinMapper; @Autowired private RoomMapper roomMapper; @Autowired private OperationLogMapper logMapper; @Transactional(rollbackFor = Exception.class) public Long checkIn(CheckinDTO dto) { // 预订转入住:条件更新预订单状态,返回 0 说明已被处理过 if (dto.getBookingId() != null) { int rows = checkinMapper.markBookingCheckedIn(dto.getBookingId()); if (rows == 0) { throw new BizException("预订单状态已变更,请刷新后重试"); } } Checkin checkin = new Checkin(); checkin.setBookingId(dto.getBookingId()); checkin.setRoomId(dto.getRoomId()); checkin.setGuestName(dto.getGuestName()); checkin.setGuestIdNo(dto.getGuestIdNo()); checkin.setCheckInTime(new Date()); checkin.setDeposit(dto.getDeposit()); checkin.setStatus(0); checkinMapper.insert(checkin); // 房态从可售或预订改为在住 roomMapper.updateStatus(dto.getRoomId(), 1); logMapper.insert(new OperationLog(dto.getOperatorId(), "CHECKIN", "入住", checkin.getId())); return checkin.getId(); } }

markBookingCheckedIn的 SQL 要带状态条件,例如UPDATE booking SET status = 1 WHERE id = #{id} AND status = 0。返回 0 时不要继续插入入住记录,否则会出现同一预订单两条在住记录。updateStatus也应该带原状态条件,避免把维修中的房间直接改成在住。换房方法可以单独写一个changeRoom(checkinId, newRoomId),事务里先校验新房间可售,再更新入住记录,最后把原房间改回可售、新房间改为在住。操作日志要记清楚“从哪个房间换到哪个房间”,答辩演示时这条日志很好用。

4.3 退房结算与账务流水:金额计算和事务边界

退房结算是最容易出金额问题的地方。正确顺序是:锁定入住记录、汇总账务流水、计算应退或应补金额、写结算流水、更新入住状态、释放房态。查询入住记录时用SELECT ... FOR UPDATE锁行,防止两个收银员同时点退房。金额统一用BigDecimal,不要用double做加减。下面代码做了简化,实际项目里还要区分押金退还、消费补收和优惠减免。

@Service public class CheckoutService { @Autowired private CheckinMapper checkinMapper; @Autowired private AccountFlowMapper accountFlowMapper; @Autowired private RoomMapper roomMapper; @Transactional(rollbackFor = Exception.class) public BigDecimal checkout(Long checkinId, Long operatorId) { // 行锁:防止并发退房,同一入住记录只能结算一次 Checkin checkin = checkinMapper.selectByIdForUpdate(checkinId); if (checkin == null || checkin.getStatus() != 0) { throw new BizException("入住记录不存在或已退房"); } // 汇总流水:押金为正,消费为负,退款为负,结算前得到余额 BigDecimal total = accountFlowMapper.sumAmountByCheckinId(checkinId); BigDecimal payable = total.max(BigDecimal.ZERO); // 写结算流水:正数表示客户补款,负数表示酒店退款 accountFlowMapper.insert(new AccountFlow( checkinId, "SETTLE", payable, operatorId)); checkinMapper.updateCheckout(checkinId, new Date()); roomMapper.updateStatus(checkin.getRoomId(), 0); return payable; } }

selectByIdForUpdate对应的 SQL 是SELECT * FROM checkin WHERE id = #{id} FOR UPDATE,它依赖数据库行锁,所以整个方法必须在事务里执行。sumAmountByCheckinId对account_flow.amount求和,正负号约定要在数据库注释和代码里保持一致。payable.max(BigDecimal.ZERO)只是示意,真实逻辑要判断余额为正时收补款、为负时退押金。退房成功后房态改回可售,但不要直接删入住记录,历史数据要保留。金额计算、流水写入、房态释放三步在同一个事务里,任何一步抛异常都整体回滚,这是退房不翻车的基本要求。

5. 避坑与常见问题:房态不一致、重复预订和金额对不上怎么排查

5.1 房态不一致:多入口更新和异常未回滚

现象是前台页面显示某间房“可售”,客房阿姨却说里面有人;或者客人已经退房,系统里还是“在住”。原因通常有三个:房态更新散落在多个 Controller 里,有的地方改了room.status但没写日志;入住和退房不在同一个事务里,订单写失败但房态已经改了;维修锁房和预订占房相互覆盖,后一次更新没有校验原状态。解决方式是把房态变更收敛到房态模块的 Service,所有入口统一调用,更新时带原状态条件,失败就抛异常回滚。再补一个每日对账 SQL:查room.status = 1但没有在住记录的房,以及有在住记录但房态不是 1 的房。发现不一致时以入住记录为准,人工修复并补一条room_status_log,不要直接改数据库了事。

5.2 重复预订:先查后插在并发下会超卖

现象是同一间房、同一个日期段出现两张有效预订单,前台只能给其中一位客人换房。原因很典型:代码先SELECT查可售,再INSERT写订单,两个请求同时查到“可售”,然后都插入成功。单机演示不容易复现,答辩时一紧张反而可能点出来。解决分两层:第一层用状态条件更新或乐观锁,占房失败就提示刷新;第二层在数据库加唯一约束或幂等键,比如用room_id + check_in_date + check_out_date + 有效状态做唯一索引,或者在预订单表加request_id防重复提交。对于小型系统,状态条件更新加唯一约束已经够用。测试时用两个浏览器同时提交同一间房,看是否只有一个成功,这个场景答辩前一定要跑。

5.3 金额对不上:浮点数、优惠顺序和押金抵扣

现象是退房金额和预订单金额差几毛甚至几块,或者押金退了两次。原因通常是金额用了double,多次加减后出现精度误差;优惠、押金、消费的抵扣顺序不固定,不同入口算出不同结果;退房接口没有幂等,重复点击写了多条结算流水。解决方式很明确:数据库金额字段用DECIMAL(10,2),Java 用BigDecimal,并且统一setScale(2, RoundingMode.HALF_UP);把计算顺序写死在 Service 里,先汇总消费、再抵扣押金、最后算补退;退房时用入住记录行锁和状态条件更新,已经退房的记录不允许再次结算。账务流水只增不改,出现差异时查流水比查余额字段靠谱得多。

5.4 权限越权与日志缺失:按钮隐藏不等于后端校验

现象是普通前台账号能通过直接输入 URL 打开订单删除页面,或者答辩老师问“这条房态是谁改的”时答不上来。原因是在 JSP 里用if隐藏了按钮,但后端接口没有校验角色;操作日志只记了登录,没有记关键业务动作。解决方式是在 Spring MVC 拦截器里统一校验登录和角色,Controller 方法上再加一层权限注解或手动判断;关键操作统一调用日志服务,记录用户、模块、动作、目标 ID 和变更摘要。权限表不用做得太复杂,用户、角色、角色菜单三张表就够。答辩时演示一个“越权访问被拦截”的场景,比只展示成功登录更加分。

5.5 演示环境翻车:硬编码路径和缺失的初始化脚本

现象是答辩现场数据库连不上、页面 404、房态列表空白。原因往往是数据库连接写死在代码里、建表语句只在自己电脑上执行过、Tomcat 版本和编译版本不一致。解决方式是提供schema.sql和data.sql,演示前先跑一遍初始化;数据库配置外置到jdbc.properties,换环境只改配置;打包用mvn clean package生成 war,提前在答辩用的机器上部署一次。我一般还会准备一个“演示数据最小集”:至少 6 间房、2 条预订单、1 条在住记录,保证从登录到退房全流程能走通。别等到现场才连数据库,这是血泪经验。

6. 验收与答辩:按这套清单跑一遍,演示不翻车

验收不要只看“页面能不能打开”,要按业务闭环走。我一般会让同学按“登录 → 房态列表 → 创建预订 → 预订转入住 → 换房 → 退房结算 → 查看账务流水和操作日志”的顺序演示一遍,再补两个异常场景:同一间房重复预订、已退房记录再次退房。下面这张清单可以直接拿去对。

验收项通过标准常见失败点
登录与权限不同角色菜单不同,越权 URL 被拦截只隐藏按钮,后端没校验
房态查询可售、在住、维修、预订状态显示正确状态值前后端不一致
创建预订占房成功写订单,失败提示刷新先查后插导致超卖
预订转入住预订单状态只变一次,入住记录唯一重复点击生成两条记录
换房原房间释放,新房间占用,日志可查只改入住表不改房态
退房结算金额与流水一致,房态释放浮点数误差、重复结算
日志与初始化关键操作有记录,空库能一键初始化只在自己电脑上能跑

答辩时老师常问三个问题:为什么用乐观锁、事务边界在哪、金额为什么用BigDecimal。回答时不要背概念,直接指着代码说“占房更新带 version,返回 0 就回滚”“退房方法整个在@Transactional里,行锁防重复结算”“金额字段是 DECIMAL,Java 用 BigDecimal 并统一保留两位”。演示前把数据库初始化脚本跑一遍,浏览器清缓存,Tomcat 日志开着,出现异常先看控制台第一行报错,不要急着改代码。从那以后我每次带毕业设计验收,都强制让同学按这条闭环走一遍,再故意制造一次重复预订和一次重复退房,能扛住这两个场景,系统基本就稳了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询