资讯动态

基于JSP+MySQL的Java滑雪场租赁管理系统设计与实现

发布时间:2026/10/10 14:44:39 来源:尧图企业网站定制
简介这是一份基于Java的滑雪场管理系统设计与实现毕业设计文档面向计算机相关专业本科生、Java初学者及需要快速搭建B/S结构管理项目的开发者。文档按标准论文体例编排从绪论、需求分析、系统设计到系统实现、测试、运行维护与结论完整呈现项目开发全流程。系统功能覆盖会员信息管理、雪具信息管理、租赁与归还管理、收银情况统计等核心场景技术方案采用JSPMySQLMyEclipseTomcat并包含课题背景、开发工具选型、关键代码示例、数据库与页面实现细节读者可直接借鉴其模块划分、业务流程与论文写作结构。资源包共1个docx文件整体约952KB文本内容精炼、目录层次完整适合离线阅读和按需检索。目前已有251人学习该资源可帮助正在做课程设计、准备毕业设计或学习Java Web开发的读者快速获得一套可复用的参考方案。1. 基于 Java 的滑雪场管理系统一套 JSP MySQL 的老架构能做什么这两年滑雪热起来之后中小雪场最头疼的往往不是造雪而是租赁窗口的秩序。旺季周末雪具大厅前排着长队工作人员一边翻 Excel 一边手写租赁单会员信息查半天同一副雪具被前后脚租出去也没人发现。这套基于 Java 的滑雪场管理系统用 JSP Servlet MySQL 的经典 B/S 架构把会员建档、雪具入库、租赁登记、归还结算、收银统计五件事串成一条线。它不适合做超大型 SaaS但正好卡在小雪场运营场景里功能边界清晰表结构简单代码能一口气看完。适合做毕业设计、课程设计也适合想从零走一遍 JSP JDBC 全流程的新手。下文把设计思路、建表脚本、关键代码和翻车点一次讲完。2. 需求与数据建模会员、雪具、租赁、收银四张表怎么设计才不留坑动手写代码之前最值得花时间的其实是数据建模。很多老项目翻车不是代码写不出来而是表结构没想清楚做到租赁模块发现字段不够用回头改表牵一发动全身。这套系统的业务不复杂核心就是四张表会员、雪具、租赁、管理员但每一张表怎么设计直接决定了后面代码的写法。2.1 为什么 B/S JSP 反而是这个小场景的省心选择这套系统用的是 B/S 结构也就是浏览器/服务器模式。用户不用安装任何客户端打开浏览器输入地址就能访问所有业务逻辑都在 Tomcat 服务器上执行页面用 JSP 动态生成。这种结构的优势很直接部署一台服务器雪具大厅、收银台、办公室的电脑都能用维护只动服务器端不用挨个机器升级。JSP 的原理值得先搞清楚。JSP 文件第一次被请求时Tomcat 会把它编译成一个 Java Servlet 类之后的请求实际上都是在执行这个 Servlet最终把生成的 HTML 返回给浏览器。所以 JSP 不是单纯的模板它本质上是 Java 代码。这套项目里页面用 JSP业务处理用 Servlet查询数据库用 JDBC是那个年代最标准的 Java Web 组合。有人会问现在都 Spring Boot Vue 了为什么还要看 JSP因为这套系统的规模决定了技术选型几张表、几个页面、一个人维护JSP 天然支持页面直出登录页面可以直接写 Java 代码查库不需要拆前后端。Spring Boot 本身不复杂但前后端分离要维护接口文档、跨域、打包部署对课程设计和中小雪场来说反而是负担。老架构不一定落后合适才是关键。2.2 E-R 拆解会员、雪具、租赁、管理员四个实体怎么落地数据库设计的第一步是概念结构设计也就是 E-R 模型。这套系统里的实体很清楚会员信息包含姓名、性别、联系电话、电子邮箱、年龄、地址雪具信息包含编号、名称、备注、价格管理员信息包含登录名和密码。真正需要动脑子的是租赁信息它不是一个独立存在的实体而是会员和雪具之间的关系。会员和雪具之间是多对多的关系一个会员可以租多件雪具一件雪具也可以被多个会员先后租用。关系型数据库不能直接存多对多必须拆成一张中间表也就是租赁表。租赁表里既要存会员的 id也要存雪具的 id还要带上租赁时间、归还时间、租赁数量、费用、是否归还这样的属性。这里有一个常见的设计误区有人喜欢在会员表里存一个「当前租了几件」的字段或者在雪具表里存一个「是否在租」这样做看似方便实际上很容易造成数据不一致。正确的做法是让租赁表成为唯一的事实来源谁租了什么、还了没有全部看租赁表的记录会员表和雪具表不做冗余的状态标记。2.3 建表规范主键、外键、允许空这三件事先定下来逻辑结构设计阶段要把 E-R 图转换成具体的表结构。这套系统的四张表设计如下表名用途核心字段说明t_admin管理员登录id, username, password系统后台登录账号t_yonghu会员信息id, yonghuming, mima, xingming, xingbie, nianling, dianhua, email, dizhi会员档案与登录账号合一t_shebei雪具信息id, bianhao, mingcheng, beizhu, jiage记录雪具种类与单价t_zulin租赁记录id, yonghu_id, shebei_id, zulin_time, guihuan_time, shuliang, feiyong, status关联会员与雪具会员表把登录账号和会员档案放在同一张表里这是小型系统常见的设计一个表搞定认证和基本信息。值得注意的是密码字段如果后续要上线建议存 MD5 或 SHA 加密后的值但作为课程设计直接存明文也能跑通理解逻辑优先。下面是会员表的建表语句CREATE TABLE t_yonghu ( id INT NOT NULL AUTO_INCREMENT COMMENT 会员编号, yonghuming VARCHAR(50) NOT NULL COMMENT 登录用户名, mima VARCHAR(50) NOT NULL COMMENT 密码, xingming VARCHAR(50) NOT NULL COMMENT 姓名, xingbie VARCHAR(10) DEFAULT NULL COMMENT 性别, nianling INT DEFAULT NULL COMMENT 年龄, dianhua VARCHAR(20) DEFAULT NULL COMMENT 联系电话, email VARCHAR(50) DEFAULT NULL COMMENT 电子邮箱, dizhi VARCHAR(100) DEFAULT NULL COMMENT 地址, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT会员信息表;这里的几个细节值得说清楚。id 用 INT 自增主键每一条会员记录都有一个唯一编号后续租赁表只需要存这个编号不需要重复存会员的姓名电话。yonghuming 和 mima 加了 NOT NULL登录功能依赖这两个字段不能为空。其他信息字段允许空因为有些会员可能不愿意留邮箱。xingbie 用 VARCHAR(10) 而不是 CHAR(1)是考虑到有些系统会存「未知」这类值VARCHAR 更灵活。表引擎用 InnoDB后面讲租赁事务时会说明原因。2.4 租赁表的冗余设计费用和归还状态放哪张表租赁表 t_zulin 是整套系统的核心它的字段设计决定了租赁、归还、收银三个模块的代码复杂度。表中 yonghu_id 和 shebei_id 是外键分别指向会员表和雪具表zulin_time 记录租出时间guihuan_time 记录归还时间shuliang 记录租了几件feiyong 记录这笔租赁应收多少钱status 用 0 和 1 表示是否已归还。feiyong 这个字段看起来是冗余的因为费用可以通过雪具表的单价乘以数量算出来但这里必须冗余存储。原因很简单雪具的价格可能会调整如果某件雪具在 1 月租出去时是 100 元3 月调价到 120 元等归还结算时如果重新查单价历史账目就全乱了。租赁发生时就把费用写死这笔账就成了历史快照调价不影响已经发生的业务这是收银系统的基本要求。status 字段用 TINYINT 类型0 表示未归还1 表示已归还。有人会问guihuan_time 为 NULL 不就代表未归还吗为什么还要单独加 status因为有些极端情况是客户赔了雪具钱结了但雪具没归还这时候业务上算已结账但归还时间还是空的。单独用 status 表达业务状态比单纯依赖时间字段更可靠查询未归还记录也直接 where status0 就行不用再判断 NULL。3. 环境搭建与登录实现从 MyEclipse 到 Tomcat 部署的完整链路数据表设计好之后下一步是把项目跑起来。这套系统的开发环境是老组合MyEclipse 6.0.1、Tomcat 6.0、MySQL 5.x对应 JDK 1.6。第一次接触老项目的人最容易卡在环境版本上所以这一章把从环境配置到登录功能的完整链路走一遍。3.1 版本对齐JDK 1.6、Tomcat 6.0、MyEclipse 6.0.1 的老三样这套项目的开发工具标注是 MyEclipse 6.0.1服务器 Tomcat 6.0数据库 MySQL 5.x。要注意Tomcat 6.0 对应 Servlet 2.5 和 JSP 2.1 规范JDK 版本一般是 1.6。如果机器上装的是 JDK 1.8部分老项目的编译配置会报错优先把编译级别调到 1.6。部署之前先确认环境变量。Tomcat 启动依赖 JAVA_HOME如果这个变量没配好startup.bat 会一闪而过日志里报找不到 Java 环境。以下命令在 Windows 控制台执行java -version echo %JAVA_HOME% cd %TOMCAT_HOME%\bin startup.batjava -version 确认 JDK 版本echo %JAVA_HOME% 看环境变量是否指向正确目录最后进入 Tomcat 的 bin 目录启动。启动后用浏览器访问 http://localhost:8080如果能看到 Tomcat 默认页面说明服务器没问题。注意 startup.bat 启动后窗口不要关关了 Tomcat 也会停。MyEclipse 里导入项目时要检查项目的编译级别和 Tomcat 运行时。右键项目选 Properties找到 Java Compiler 把 Compiler compliance level 调到 1.6再在 Targeted Runtimes 里勾选 Tomcat 6.0。这些设置如果不一致代码可能编译通过但部署后运行报错这是老项目的经典翻车原因。3.2 建库脚本四张业务表一次到位环境准备好之后先把数据库建起来。这套系统的脚本可以直接在 MySQL 里执行包含建库、建表、插入默认管理员三个部分CREATE DATABASE IF NOT EXISTS ski DEFAULT CHARACTER SET utf8mb4; USE ski; CREATE TABLE t_shebei ( id INT NOT NULL AUTO_INCREMENT COMMENT 雪具编号, bianhao VARCHAR(50) NOT NULL COMMENT 雪具编号(业务编号), mingcheng VARCHAR(100) NOT NULL COMMENT 雪具名称, beizhu VARCHAR(200) DEFAULT NULL COMMENT 备注, jiage DECIMAL(10,2) NOT NULL COMMENT 租赁单价, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT雪具信息表; CREATE TABLE t_zulin ( id INT NOT NULL AUTO_INCREMENT COMMENT 租赁记录编号, yonghu_id INT NOT NULL COMMENT 会员ID, shebei_id INT NOT NULL COMMENT 雪具ID, zulin_time DATETIME NOT NULL COMMENT 租赁时间, guihuan_time DATETIME DEFAULT NULL COMMENT 归还时间, shuliang INT NOT NULL DEFAULT 1 COMMENT 租赁数量, feiyong DECIMAL(10,2) NOT NULL COMMENT 租赁费用, status TINYINT NOT NULL DEFAULT 0 COMMENT 0未归还 1已归还, PRIMARY KEY (id), KEY idx_yonghu (yonghu_id), KEY idx_shebei (shebei_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT租赁信息表; CREATE TABLE t_admin ( id INT NOT NULL AUTO_INCREMENT COMMENT 管理员编号, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(50) NOT NULL COMMENT 密码, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT管理员表; INSERT INTO t_admin (username, password) VALUES (admin, admin123);这个脚本里有两个细节值得注意。一是所有表都用 InnoDB不用 MyISAM因为租赁业务涉及多表更新必须支持事务InnoDB 才有事务能力。二是 t_zulin 表在 yonghu_id 和 shebei_id 上各建了一个普通索引因为查询经常按会员或雪具筛选没有索引数据量大了之后会全表扫描。DECIMAL(10,2) 用于金额避免用 FLOAT 造成的精度丢失这也是收银系统的基本要求。3.3 JDBC 封装DBUtil 的连接建立与关闭数据库连接是 JSP 页面和 Servlet 访问数据库的通道。整套系统里很多地方都要查库不可能每个页面都写一遍连接代码所以要用一个工具类统一封装。这个类就是典型的 DAO 工具类也是 Java 设计模式里说的「封装变化」把数据库驱动加载、连接获取、资源关闭集中管理。package com.ski.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/ski?useUnicodetruecharacterEncodingutf8useSSLfalse; private static final String USER root; private static final String PASSWORD root; // 加载驱动,只执行一次 static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } // 获取连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 关闭资源,顺序:rs - stmt - conn public static void close(ResultSet rs, Statement stmt, Connection conn) { try { if (rs ! null) rs.close(); if (stmt ! null) stmt.close(); if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }Class.forName(DRIVER) 的作用是加载 MySQL 驱动类到 JVM这段代码放在静态块里保证类加载时只执行一次。URL 里的 useUnicodetruecharacterEncodingutf8 是处理中文乱码的关键useSSLfalse 是因为本地开发环境不需要 SSL 加密。USER 和 PASSWORD 按本机 MySQL 的实际配置修改。这套代码用的是最原始的 JDBC 驱动没有引入连接池。对于毕业设计和小型雪场系统完全够用因为并发量低每次请求创建连接、用完就关反而是最不容易出错的写法。如果以后要上生产环境可以考虑换成 C3P0 或 Druid 连接池但那是后话。3.4 登录模块串联JSP 表单、Servlet、Session 一条线走通登录是整套系统的入口它的实现串联了 JSP 表单、Servlet 和 Session 三个关键点。页面流程是这样的浏览器访问 login.jsp填写用户名密码提交到 LoginServletServlet 查数据库验证成功就把用户信息写进 Session 并跳转到主页面失败则返回登录页并提示错误。protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); // 使用 PreparedStatement 防止 SQL 注入 String sql SELECT * FROM t_admin WHERE username? AND password?; ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); rs ps.executeQuery(); if (rs.next()) { HttpSession session request.getSession(); session.setAttribute(admin, username); // 登录标记写入 Session response.sendRedirect(index.jsp); // 重定向到主页面 } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(rs, ps, conn); } }这里有两个必须讲清楚的点。PreparedStatement 用问号占位符绑定参数而不是拼 SQL 字符串这是防止 SQL 注入的标准做法也是 Java 面试里反复问的八股点。成功后用 sendRedirect 重定向失败用 forward 转发区别在于重定向是浏览器重新发起一次请求地址栏会变化转发是服务器内部跳转地址栏不变登录失败用转发可以保留错误提示信息。Session 的作用是记录登录状态服务端把用户信息保存在内存里只给浏览器发一个 sessionid。这套系统后续所有页面判断「是否已登录」就是检查 session 里有没有 admin 这个属性。这里不用 Cookie 直接存用户名是因为 Cookie 存在客户端可以被篡改Session 在服务端更安全。4. 租赁、归还与收银统计把核心业务跑通的三个关键点登录只是敲门砖这套系统的真正业务核心是租赁、归还和收银统计。这三个模块环环相扣租赁产生记录归还结算费用收银统计汇总收入任何一环出了差错账目就对不上。这一章把三个模块的实现逻辑和关键代码写清楚。4.1 租赁业务的事务边界多条 SQL 必须同生共死租赁业务的完整操作是插入一条租赁记录到 t_zulin同时把对应的雪具标记为已租出。如果只用一条插入语句会出现一个问题租赁记录写成功了但雪具状态没有更新同一件雪具可能被下一个顾客再租一次。这种数据不一致问题根源在于多条 SQL 语句没有放在同一个事务里。Java 里 JDBC 操作默认是自动提交的每条 SQL 执行完就立即生效。要保证多步操作的一致性必须手动关闭自动提交把多条 SQL 放在一个事务里全部成功才提交任何一步失败都回滚。public void zulin(int yonghuId, int shebeiId, int shuliang, double jiage) { Connection conn null; PreparedStatement ps1 null; PreparedStatement ps2 null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 第一步:插入租赁记录 String insertSql INSERT INTO t_zulin(yonghu_id, shebei_id, zulin_time, shuliang, feiyong, status) VALUES(?, ?, NOW(), ?, ?, 0); ps1 conn.prepareStatement(insertSql); ps1.setInt(1, yonghuId); ps1.setInt(2, shebeiId); ps1.setInt(3, shuliang); ps1.setDouble(4, jiage * shuliang); // 费用按租赁时单价快照 ps1.executeUpdate(); // 第二步:把雪具标记为已租出,条件里带 zhuangtai0 防止重复租 String updateSql UPDATE t_shebei SET zhuangtai1 WHERE id? AND zhuangtai0; ps2 conn.prepareStatement(updateSql); ps2.setInt(1, shebeiId); int count ps2.executeUpdate(); if (count 0) { throw new RuntimeException(雪具已被租出,请刷新后重试); } conn.commit(); // 全部成功,提交事务 } catch (Exception e) { try { if (conn ! null) conn.rollback(); // 任何异常,回滚全部 } catch (SQLException ex) { ex.printStackTrace(); } throw new RuntimeException(租赁失败: e.getMessage()); } finally { DBUtil.close(null, ps2, conn); if (ps1 ! null) try { ps1.close(); } catch (SQLException e) { } } }这段代码里的两个细节非常实用。setAutoCommit(false) 之后commit 和 rollback 之间所有 SQL 成为一个整体这就是事务的原子性。UPDATE 语句里带 zhuangtai0 这个条件利用数据库的行锁和影响行数来做并发控制两个收银员同时租同一件雪具时只有一个能成功这是避免超卖的最简单方案。面试里问的「Java 怎么保证数据一致性」在业务层面就是这个答案事务 条件更新。4.2 归还登记超时费用与状态更新的处理顺序归还模块的逻辑和租赁刚好相反把租赁记录的 status 改为已归还写入归还时间同时把雪具状态改回空闲。如果系统按次收费费用在租赁时已经算好归还时只需要更新状态如果按时长收费归还时还要补算费用。先看按时长计费的场景。租赁时费用先按预估存了一笔归还时根据实际时长重新计算多退少补。计算逻辑是拿归还时间减去租赁时间得到小时数再乘以单价和数量。public void guihuan(int zulinId) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 先查出租赁记录,获取雪具ID和租赁时间 String querySql SELECT shebei_id, zulin_time FROM t_zulin WHERE id?; ps conn.prepareStatement(querySql); ps.setInt(1, zulinId); rs ps.executeQuery(); if (!rs.next()) { throw new RuntimeException(租赁记录不存在); } int shebeiId rs.getInt(shebei_id); java.util.Date zulinTime rs.getTimestamp(zulin_time); // 按小时结算费用:时长(小时) * 单价 * 数量 long diff System.currentTimeMillis() - zulinTime.getTime(); double hours Math.ceil(diff / (60.0 * 60 * 1000)); // 不足1小时按1小时计 double unitPrice getShebeiPrice(shebeiId); double fee hours * unitPrice; // 更新租赁记录:已归还,写入归还时间,更新费用 String updateZulin UPDATE t_zulin SET status1, guihuan_timeNOW(), feiyong? WHERE id?; ps conn.prepareStatement(updateZulin); ps.setDouble(1, fee); ps.setInt(2, zulinId); ps.executeUpdate(); // 释放雪具 String updateShebei UPDATE t_shebei SET zhuangtai0 WHERE id?; ps conn.prepareStatement(updateShebei); ps.setInt(1, shebeiId); ps.executeUpdate(); conn.commit(); } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } throw new RuntimeException(归还失败: e.getMessage()); } finally { DBUtil.close(rs, ps, conn); } }归还逻辑里最关键的是操作顺序先查原租赁记录再计算费用然后更新租赁表最后释放雪具整个流程在同一个事务里。如果只改租赁表不释放雪具雪具会一直显示被占用如果先释放雪具再改租赁表万一费用更新失败雪具已经被别人租走账就乱了。按这里的事务顺序执行任何一步失败都可以整体回滚。4.3 收银统计一条 GROUP BY 搞定日报和月报收银统计模块不需要复杂代码核心就是一条按时间分组的聚合查询。管理员选择统计日期系统把当天所有已归还租赁记录的费用汇总生成日报表。SQL 写法如下-- 按天统计当日收入 SELECT DATE(zulin_time) AS day, SUM(feiyong) AS total FROM t_zulin WHERE status 1 AND DATE(zulin_time) CURDATE() GROUP BY DATE(zulin_time); -- 按月统计收入 SELECT DATE_FORMAT(zulin_time, %Y-%m) AS month, SUM(feiyong) AS total FROM t_zulin WHERE status 1 GROUP BY DATE_FORMAT(zulin_time, %Y-%m);这里有几个细节。status1 的条件表示只统计已经归还结算的记录因为未归还的只算押金不算收入。SUM(feiyong) 直接对租赁时冗余存储的费用求和不用再去关联雪具表查单价这也是前一章说费用要冗余的原因。GROUP BY DATE(zulin_time) 按天分组DATE_FORMAT(zulin_time, %Y-%m) 按月分组在 JSP 里把 ResultSet 循环遍历输出成表格即可。如果以后数据量大了这个统计会慢常见做法是在 zulin_time 字段上建索引或者按月建分区表。但对这套系统来说一天几千条租赁记录撑死了单表查询完全够用不需要过度设计。4.4 表单细节下拉框、日历控件与输入校验租赁页面需要选择一个会员和一件雪具如果让用户手输名称或编号容易出错界面上通常用两个下拉框选项从数据库里动态查出来。会员下拉框显示姓名value 存会员 id雪具下拉框显示名称value 存雪具 id。提交后 Servlet 拿到的是 id再按 id 插入租赁表。时间的输入用日历控件老项目里常见的是 My97DatePicker一个日历弹层点一下日期就能填充到文本框。如果你手里这份源码里没有现成的用浏览器原生input typedatetime-local也能替代效果差不多。输入校验要两端都做。前端 JS 校验的作用是即时提示比如租赁数量必须大于 0、会员不能为空减轻服务器压力服务端必须再校验一次因为前端的校验可以被绕过。一个简单的 JS 校验如下function checkForm() { var yonghuId document.getElementById(yonghuId).value; var shebeiId document.getElementById(shebeiId).value; var shuliang document.getElementById(shuliang).value; if (yonghuId || shebeiId ) { alert(请选择会员和雪具); return false; } if (isNaN(shuliang) || parseInt(shuliang) 0) { alert(租赁数量必须为正整数); return false; } return true; }这段 JS 在表单提交时触发如果返回 false 则阻止提交。isNaN 判断输入是否为数字parseInt 转成整数后判断是否大于 0。服务端对应的逻辑也不能少比如数量字段解析失败就抛异常用户没选择下拉框就提示这些检查能避免脏数据写进数据库。5. 避坑排查老 JSP 系统最容易翻车的五个细节这套系统整体不复杂但它踩的坑很典型。下面五条是我在部署和改造类似项目时反复遇到的每一条都按「现象、原因、解决」来写遇到问题可以直接对号入座。5.1 中文乱码从 JSP 页面到 MySQL 的三层编码现象是页面输入中文后存进数据库变成问号或者从数据库查出来的中文显示乱码。这个问题的根源在于编码没有统一JSP 页面、请求传输、数据库连接三个环节各用各的编码字符在转换过程中丢失了。解决方法是三层全部统一为 UTF-8。第一层JSP 文件头部写pageEncodingUTF-8。第二层Servlet 里在读取参数之前执行request.setCharacterEncoding(UTF-8)。第三层数据库连接 URL 带上characterEncodingutf8就像 DBUtil 里写的那样。如果还乱检查 MySQL 表本身的字符集确认建表时用的是 utf8mb4。最省事的做法是在 web.xml 里配一个编码过滤器所有请求统一经过过滤filter filter-nameencodingFilter/filter-name filter-classcom.ski.filter.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping这个配置让所有请求都先经过过滤器把请求和响应的编码统一设置成 UTF-8。实际项目里过滤器类里核心代码就一句request.setCharacterEncoding(encoding)和response.setCharacterEncoding(encoding)。配好之后乱码问题基本绝迹。5.2 ClassNotFoundExceptionmysql-connector 驱动没到位现象是系统启动后第一次点登录或查数据库页面报 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。这个异常的意思是 JVM 找不到 MySQL 驱动类。原因通常是 mysql-connector-java 这个 jar 包没有放到 Tomcat 部署目录的 WEB-INF/lib 下面只在 MyEclipse 的构建路径里有。解决方法是把 mysql-connector-java-5.1.x.jar 拷贝到项目的 WebRoot/WEB-INF/lib 目录下然后在 MyEclipse 里刷新工程重新发布到 Tomcat。这里有个常见误区有人把 jar 放到了 Tomcat 的 lib 目录觉得一定能加载但 Web 应用优先从自己的 WEB-INF/lib 加载类项目里没放就是这个结果。发布之后可以去 Tomcat 的 webapps 目录检查一下确认 jar 真的在部署后的 WEB-INF/lib 里。5.3 数据不一致租赁单写成功但雪具状态没改现象是租赁记录在列表里能看到但雪具的「在租」状态没有更新导致同一件雪具被租给两个人。原因就是第 4 章讲的事务问题插入租赁记录和更新雪具状态两条 SQL 没有放在同一个事务里第一条成功后第二条失败数据就分裂了。解决方法是把两步操作包进事务参考 4.1 的代码setAutoCommit(false) 开启全部成功再 commit任何异常 rollback。另外还要检查一点租赁模块里调用更新雪具的 SQL 后有没有判断影响行数。如果那条 UPDATE 因为条件不匹配影响了 0 行必须视为失败并回滚否则即使有事务业务上还是错的。5.4 404 与路径大小写Tomcat 的严格匹配现象是某个页面点击跳转后报 404但文件明明存在。原因大概率是路径大小写不一致。Tomcat 对 URL 和文件名的匹配区分大小写如果 JSP 文件名是 index.jsp代码里跳转到 Index.jsp就会 404。这在 Windows 本地开发时不容易暴露因为文件系统不区分大小写但 Tomcat 映射 URL 时仍然区分。解决方法是统一命名规范所有 JSP 文件名、Servlet 路径、跳转 URL 全部用小写并且跳转路径和实际文件名逐字符比对一遍。检查重点在 getRequestDispatcher 的参数、sendRedirect 的参数、以及表单 action 里写的路径。这套系统页面不多排查起来很快别一上来就怀疑配置问题。5.5 Session 不同步改完密码还在旧会话现象是管理员修改密码成功后过一会儿用新密码登录正常但当前会话里的信息还是旧密码某些基于权限的判断出现异常。原因是只更新了数据库没有更新 Session 里保存的用户数据。Session 里存的还是登录那一刻的快照数据库改了它不知道。解决方法是修改密码成功后重新从数据库查出最新用户信息再执行一次 session.setAttribute 覆盖旧值如果 Session 里存的东西不多也可以直接 session.invalidate()然后跳转到登录页让用户重新登录更干净。这个问题在答辩和面试时经常被拿来问 Session 的生命周期记住一句话Session 是服务端内存里的对象数据库的修改不会自动同步进去。6. 验收技巧30 分钟回归流程与收银对账的土办法系统改完或部署完别急着上线先按真实业务把流程完整走一遍。我有一套固定的回归流程每次部署这套系统都会强制过一遍30 分钟以内能跑完能把主要功能点全部覆盖。6.1 按流程走一遍的验证路径先准备测试数据新增三个会员新增三件雪具。然后按这个顺序操作用 admin 登录系统会员模块里查一下刚才新增的会员能查到且信息无乱码雪具模块确认三件雪具都在库给会员 1 租雪具 1给会员 2 租雪具 2再试着租雪具 1系统应该提示已被租出归还雪具 1确认雪具状态变回空闲打开收银统计看一眼今天的收入是否为雪具 1 的费用最后修改密码退出系统用新密码重新登录。整个过程走下来没报错这套系统基本可以交付。6.2 收银对账技巧用租赁明细核对统计报表收银统计模块可能被改出问题所以单独提供一个对账方法。打开收银统计页面之前先在 MySQL 里手工跑一条查询看租赁表里所有已归还记录的费用总和SELECT SUM(feiyong) AS total, COUNT(*) AS cnt FROM t_zulin WHERE status 1;这个 SUM 是直接从原始租赁记录算出来的不经过任何统计模块的加工。如果它和收银统计页面显示的数字一致说明统计逻辑没问题如果不一致说明统计模块的查询条件、时间范围或状态判断有 bug。这套土办法不依赖任何工具一条 SQL 就能验证账目简单可靠。以前我拿到这种老项目爱直接改代码后来发现很多 bug 是改出来的不如先花 30 分钟把完整流程跑一遍确认基线没问题再动刀。现在每次部署完我都强制自己先过一遍上面这个回归流程再去碰业务逻辑。希望这套验证路径和踩坑记录能帮到你省下几个晚上的排查时间。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑