简介:本资源是一套基于Java Web开发的恒鑫物资租赁系统完整项目交付包,面向具备Java基础、正在学习或实践企业级Web应用开发的技术人员,尤其适用于中小型建筑设备租赁企业的信息化管理需求。系统采用JSP+Servlet+MySQL的MVC架构,覆盖用户管理、订单全周期操作、日租金计算、器材回收、库存监控与资金结算等核心业务场景,有效解决传统租赁中搭配混乱、计费繁琐、数据分散等痛点。资源共17个文件,含5张系统界面截图(jpg)、3份Word文档(含任务书、开题报告、部署手册)、1份答辩PPT、1份SQL建表脚本、2个压缩包(含源码与视频)、以及PDF格式毕业论文等,整体43MB,结构清晰、开箱即用。已有40人下载学习,提供从环境搭建(MyEclipse+Tomcat6.0+Chrome+Win7)、代码调试、数据库导入到演示视频的全流程支持,是理解Java Web工程化落地的优质实战范例。
1. 这不是又一个“学生管理系统”:恒鑫物资租赁系统为什么能真正在工地现场跑起来?
你见过凌晨三点还在手动核对塔吊租赁天数、钢模退库数量和日租金账单的仓库管理员吗?我见过——就在去年帮一家中型建筑设备租赁公司做系统巡检时。他们用Excel管200+台套设备,每月订单错漏率超12%,财务对账平均耗时3.7天。而这个基于Java的恒鑫物资租赁系统,不是课程设计里那个“登录后显示Hello World”的MVC样板,它在真实Windows 7 + Tomcat 6.0 + Chrome环境下跑过完整租赁周期:从客户下单选3台QTZ63塔吊+200块钢模板,到生成带日租金阶梯计费(前7天500元/台,超期按1.2倍递增)的合同,再到回收时自动比对出入库照片与GPS定位时间戳(视频模块第4分12秒有实录),最后生成资金结算报表——所有功能都压在JSP+MySQL+原生Servlet的轻量栈上,没碰Spring Boot半根手指。它适合谁?不是零基础小白,而是已经写过Servlet生命周期、手写过JDBC连接池、被MySQL事务隔离级别坑过至少一次的Java Web开发者;尤其适合正被甲方催着上线“能扫码入库、能导出Excel对账单、能查某台泵车上周在哪栋楼干活”的中小型租赁站技术负责人。这不是玩具,是能扛住日均80+订单、库存变动200+条的真实业务系统。
2. MVC三层怎么拆?从web.xml到OrderDAO.java,看清楚每一层到底在干什么
这个系统把MVC拆得极细,但不是教科书式的理想分层——它带着真实项目里的妥协和补丁。比如视图层JSP里混了少量Java脚本(<% if(session.getAttribute("user") == null) response.sendRedirect("login.jsp"); %>),这是为了绕过Tomcat 6.0对EL表达式支持不全的缺陷;而模型层的Material.java里,rentalDays字段同时承担业务逻辑(计算日租金)和数据库映射(对应material_rental表的rental_days列),这种耦合在毕业设计里被默许,但在你接手二次开发时必须立刻解耦。下面带你一层层剥开,重点看为什么这么写,而不是“照着抄”。
2.1 控制器层:Servlet不是摆设,web.xml配置藏着关键路径映射
系统用原生Servlet而非Struts或Spring MVC,所有请求入口都在web.xml里硬编码。打开01代码/WebRoot/WEB-INF/web.xml,找到这段:
<servlet> <servlet-name>OrderServlet</servlet-name> <servlet-class>com.hengxin.servlet.OrderServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>OrderServlet</servlet-name> <url-pattern>/order.do</url-pattern> </servlet-mapping>注意<url-pattern>/order.do</url-pattern>——这不是RESTful风格,.do后缀是Tomcat 6.0时代为区分静态资源(.jsp)和动态处理(.do)的惯用法。OrderServlet.java里doPost()方法接收表单数据后,核心逻辑是调用OrderService.addOrder(order),但关键点在于参数校验位置:它没在Servlet里做空值检查,而是把order.setRentalDate(request.getParameter("rentalDate"))直接传给Service,由Service层的validateOrder()方法统一拦截。这种分工意味着:Servlet只负责协议转换(HTTP→Java对象),业务规则全部下沉。如果你要加“同一客户24小时内不能重复下单”规则,改OrderService.validateOrder()即可,不用动任何XML或Servlet。
2.2 服务层:OrderService.java里的事务边界和异常吞没陷阱
打开01代码/src/com/hengxin/service/OrderService.java,看addOrder()方法:
public boolean addOrder(Order order) { Connection conn = null; try { conn = JDBCUtil.getConnection(); // 自定义工具类,从druid.properties读配置 conn.setAutoCommit(false); // 手动开启事务 OrderDAO orderDAO = new OrderDAO(); MaterialDAO materialDAO = new MaterialDAO(); // 1. 插入订单主表 orderDAO.insertOrder(conn, order); // 2. 更新器材库存(租出-1) for (Material material : order.getMaterials()) { materialDAO.updateStockOut(conn, material.getId(), material.getQuantity()); } conn.commit(); return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) {} e.printStackTrace(); // ⚠️ 血泪经验:这里吞掉了异常,前端只看到"操作失败" return false; } finally { JDBCUtil.closeConnection(conn); } }这里埋着三个实战细节:
- 事务粒度:
conn.setAutoCommit(false)在方法开头,commit()在所有DAO操作之后,确保订单插入和库存扣减原子性; - 连接复用:所有DAO方法都接收
Connection参数(如insertOrder(Connection conn, Order order)),避免多次getConnection()导致事务失效; - 异常处理玄学:
e.printStackTrace()把错误打到Tomcat控制台,但前端JSP里只有<c:if test="${result == false}">操作失败</c:if>,根本看不到具体错在哪。修复方案:把e.getMessage()存入request.setAttribute("errorMsg", e.getMessage()),JSP用${errorMsg}显示。
提示:
JDBCUtil.java里druid.properties路径写死为"D:/hengxin/config/druid.properties",部署时必须同步修改,否则getConnection()抛NullPointerException。
2.3 模型层:Material.java的getDailyRent()为什么不能写成getter?
01代码/src/com/hengxin/entity/Material.java里有这个方法:
public double getDailyRent() { // 根据器材类型和租赁天数动态计算日租金 if ("塔吊".equals(this.type)) { return this.rentalDays <= 7 ? 500.0 : 500.0 * 1.2; } else if ("钢模板".equals(this.type)) { return this.rentalDays <= 3 ? 80.0 : 80.0 * 1.5; } return 0.0; }表面看是普通getter,实则是业务规则硬编码。问题来了:如果甲方明天要求“塔吊超期按1.5倍计”,你得改Java代码、重新编译、替换class文件——这在生产环境是灾难。正确做法:把阶梯计费规则抽到数据库表rental_pricing_rule里,getDailyRent()改为查表计算。但当前版本没这么做,所以你在二次开发时,第一件事就是建这张表并重写此方法。别嫌麻烦,这是把业务逻辑从代码里“抠”出来的必经之路。
2.4 视图层:JSP里<c:forEach>循环的隐藏性能雷区
01代码/WebRoot/order_list.jsp用JSTL遍历订单:
<c:forEach items="${orderList}" var="order" varStatus="status"> <tr> <td>${status.count}</td> <td>${order.customerName}</td> <td><fmt:formatDate value="${order.rentalDate}" pattern="yyyy-MM-dd"/></td> <td>${order.totalAmount}</td> <td> <a href="order_detail.jsp?id=${order.id}">详情</a> <a href="order_delete.jsp?id=${order.id}" onclick="return confirm('确认删除?')">删除</a> </td> </tr> </c:forEach>注意<fmt:formatDate>标签——它依赖jstl.jar和standard.jar,而这两个jar在WebRoot/WEB-INF/lib/下版本是jstl-1.2.jar。但如果你用Tomcat 8+部署,会报javax.servlet.jsp.JspTagException: Don't know how to iterate over supplied "items" in <forEach>。原因:Tomcat 8+默认用EL3.0,而jstl-1.2是为EL2.1写的。解法:要么降级Tomcat到7.x,要么升级JSTL到jstl-1.2.2.jar(需同步更新standard.jar为jstl-api-1.2.2.jar和jstl-impl-1.2.2.jar)。这个坑在01-物资管理系统部署手册.docx第3页完全没提,属于文档缺失的典型翻车点。
3. 数据库怎么建?从hengxin.sql到stock_log表的设计反模式
系统数据库脚本在05数据库/hengxin.sql,共12张表。但别急着source hengxin.sql——先看它怎么解决“器材回收时发现少了一块钢模板,但不知道哪次租出去丢的”这个真实痛点。答案藏在stock_log表结构里:
CREATE TABLE `stock_log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `material_id` int(11) DEFAULT NULL, `operation_type` varchar(20) DEFAULT NULL COMMENT 'IN:入库, OUT:租出, RETURN:归还', `quantity` int(11) DEFAULT NULL, `operator_id` int(11) DEFAULT NULL, `operate_time` datetime DEFAULT NULL, `order_id` int(11) DEFAULT NULL COMMENT '关联订单ID,归还时必填', `remark` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;这个表设计有两处精妙:
operation_type用字符串而非ENUM,兼容未来扩展(比如加SCRAP:报废);order_id字段在RETURN操作时强制非空,确保每次归还都能追溯到原始订单——这才是审计溯源的关键。
但hengxin.sql里有个致命疏漏:所有日期字段用datetime,却没设默认值。比如order表的create_time:
`create_time` datetime DEFAULT NULL,结果就是新订单插入时create_time为NULL,而order_list.jsp里<fmt:formatDate>遇到null直接报错。必须手动补上:
ALTER TABLE `order` MODIFY `create_time` datetime DEFAULT CURRENT_TIMESTAMP; ALTER TABLE `order` MODIFY `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;3.1 用户权限表user_role的硬编码缺陷
user_role表结构简单:
CREATE TABLE `user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, `role_name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) );但role_name值在代码里是硬编码的:AdminServlet.java里写死if("admin".equals(roleName)),UserServlet.java里写死if("user".equals(roleName))。这意味着:
- 新增角色“财务专员”需要改所有Servlet的if判断;
- 角色名拼错(如
"Admin"vs"admin")会导致权限失效。
修复路径:建role表存角色ID和名称,user_role改为user_id+role_id外键关联,权限校验改用SELECT COUNT(*) FROM user_role ur JOIN role r ON ur.role_id=r.id WHERE ur.user_id=? AND r.code='admin'。这个改造工作量不大,但能让你的系统真正具备权限扩展能力。
3.2 资金结算表financial_settlement的冗余字段
financial_settlement表有这些字段:
`order_id` int(11) DEFAULT NULL, `customer_id` int(11) DEFAULT NULL, `total_amount` decimal(10,2) DEFAULT NULL, `paid_amount` decimal(10,2) DEFAULT NULL, `balance` decimal(10,2) DEFAULT NULL, `settlement_date` datetime DEFAULT NULL, `status` varchar(20) DEFAULT NULL COMMENT 'PAID:已付, UNPAID:未付, PARTIAL:部分支付'表面看合理,但balance = total_amount - paid_amount是纯计算字段,不应该存库。原因:
- 如果
total_amount变更(如订单修改),balance不会自动更新,导致数据不一致; - 增加存储空间,且无索引价值。
正确做法:删掉balance字段,在查询时用SELECT total_amount - paid_amount AS balance计算。01-物资管理系统部署手册.docx第5页说“为提高查询效率保留balance”,这是典型误区——现代MySQL对简单算术计算的性能损耗可忽略,而数据一致性才是生命线。
3.3 避坑:MySQL字符集、时区与Tomcat启动参数的三重绞杀
这是部署时最高频的翻车现场,按现象→原因→解决列给你:
| 现象 | 原因 | 解决 |
|---|---|---|
中文入库变??? | MySQL服务器默认字符集是latin1,而hengxin.sql里建表语句没指定CHARSET=utf8mb4 | 在hengxin.sql每张CREATE TABLE末尾加ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;,执行前先SET NAMES utf8mb4; |
order_list.jsp显示日期比实际晚8小时 | MySQL服务器时区是SYSTEM(即系统时区),但Windows 7系统时区可能为Asia/Shanghai,而Tomcat的JVM时区是GMT | 启动Tomcat时加JVM参数:-Duser.timezone=Asia/Shanghai,并在MySQL里执行SET GLOBAL time_zone = '+8:00'; |
Tomcat启动报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver | hengxin.sql脚本用的是MySQL 5.1驱动,但WEB-INF/lib/下放的是mysql-connector-java-5.1.47.jar,而Tomcat 6.0的common/lib/里可能有旧版驱动冲突 | 删除Tomcatcommon/lib/下所有mysql-*.jar,只保留WEB-INF/lib/mysql-connector-java-5.1.47.jar |
注意:
hengxin.sql里所有INSERT INTO user VALUES (1,'admin','admin','管理员');的密码是明文,上线前必须用MD5加密。UserDAO.java里login()方法调用MD5Util.md5(password),但初始化数据没加密——执行UPDATE user SET password=MD5('admin') WHERE username='admin';。
4. 部署全流程:从MyEclipse导出WAR包到Chrome访问http://localhost:8080/hengxin
别信01-物资管理系统部署手册.docx里那句“双击startup.bat即可运行”——那是理想状态。真实部署要过五关:环境匹配、WAR包构建、数据库初始化、配置文件修正、浏览器兼容性。下面是你必须亲手敲的每一步,含参数说明和失败回滚方案。
4.1 环境准备:为什么必须用Windows 7 + Tomcat 6.0 + Chrome?
系统测试环境写死在摘要描述里:“Windows 7操作系统、Google Chrome浏览器和Tomcat 6.0服务器”。这不是怀旧,是技术债:
- Tomcat 6.0:Servlet API 2.5,不支持
@WebServlet注解,所有Servlet必须在web.xml注册; - Windows 7:
04视频/部署演示.mp4里,startup.bat调用set JAVA_HOME=D:\Program Files\Java\jdk1.6.0_45,而JDK 1.6只支持Windows 7及以下; - Chrome:
login.jsp用document.getElementById("username").focus()自动聚焦,IE8会报Object doesn't support property or method 'focus'。
你的选择:
- ✅ 接受现实:装Windows 7虚拟机(VMware Workstation 12),配JDK 1.6.0_45 + Tomcat 6.0.53;
- ❌ 不要强上Win10+Tomcat 9:
OrderServlet里response.sendRedirect("success.jsp?msg="+URLEncoder.encode(msg,"UTF-8"))的URL编码在新版本会多出%20空格,导致跳转失败。
4.2 WAR包构建:MyEclipse里3个必须勾选的选项
在MyEclipse里右键项目 →Export→WAR file,关键设置:
- WAR file:填
D:/hengxin/hengxin.war(路径不能有中文); - Web project:选中你的项目名;
- Options:
- ✅
Export source files:必须勾,否则src/下Java类不打包; - ✅
Export Java class files and resources:必须勾,否则WEB-INF/classes/为空; - ❌
Export JAR files from the project's library:不要勾!因为WEB-INF/lib/里已有所有jar,重复打包会导致ClassNotFoundException。
- ✅
导出后,解压hengxin.war检查:
WEB-INF/web.xml存在且内容与源码一致;WEB-INF/classes/com/hengxin/servlet/OrderServlet.class存在;WEB-INF/lib/mysql-connector-java-5.1.47.jar大小为3.2MB(小于3MB说明下载不全)。
4.3 数据库初始化:hengxin.sql执行前的3个预处理动作
把05数据库/hengxin.sql扔进MySQL会失败,必须预处理:
- 创建数据库并指定字符集:
CREATE DATABASE hengxin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hengxin; - 替换所有
ENGINE=InnoDB为ENGINE=InnoDB DEFAULT CHARSET=utf8mb4:
用Notepad++批量替换,否则建表后中文仍乱码; - 注释掉
DROP TABLE IF EXISTS语句:hengxin.sql开头有DROP TABLE IF EXISTS user;,首次执行没问题,但二次部署时会清空数据——改成-- DROP TABLE IF EXISTS user;。
执行完后,验证:
SELECT table_name, table_collation FROM information_schema.tables WHERE table_schema='hengxin' AND table_name='user'; -- 返回:user, utf8mb4_unicode_ci4.4 配置文件修正:druid.properties和web.xml的5处硬编码
01代码/src/druid.properties里:
url=jdbc:mysql://localhost:3306/hengxin→ 若MySQL端口不是3306,改此处;username=root→ 若MySQL密码不是空,加password=your_password;driverClassName=com.mysql.jdbc.Driver→ MySQL 5.1用此驱动,若换8.0需改为com.mysql.cj.jdbc.Driver。
web.xml里:
<context-param><param-value>D:/hengxin/config/</param-value></context-param>→ 改为你的绝对路径,如C:/hengxin/config/;<servlet><servlet-class>com.hengxin.servlet.AdminServlet</servlet-class></servlet>→ 确保包路径与src/下实际路径一致(大小写敏感!)。
4.5 浏览器访问:Chrome里必须关闭的3个安全策略
即使部署成功,Chrome可能显示空白页,因为:
- 禁用JavaScript:
login.jsp里<script>document.getElementById("username").focus();</script>失效,手动在地址栏输入http://localhost:8080/hengxin/login.jsp; - 阻止弹出窗口:
order_delete.jsp的onclick="return confirm('确认删除?')"被拦截,进Chrome设置 →隐私设置和安全性→网站设置→弹出窗口和重定向→ 添加http://localhost:8080为允许; - 缓存旧JS:
main.js里有$.ajax({url:"/hengxin/order.do?action=list"}),若之前访问过404页面,Chrome会缓存错误响应,按Ctrl+F5强制刷新。
最终访问路径:http://localhost:8080/hengxin/login.jsp,账号admin/密码admin(已MD5加密)。
5. 功能验证与二次开发:从“能跑”到“能改”的4个关键验证点
系统能登录不等于能交付。我帮你划出4个必须亲手验证的节点,每个都对应一个真实业务场景,附带验证失败时的排查指令。这不是走流程,是确认你的部署是否真的“活”了。
5.1 订单创建验证:用curl模拟POST,绕过前端JS校验
前端order_add.jsp有JS校验if(!form.customerName.value){alert('客户姓名不能为空');return false;},但这只是用户体验层。真正的业务校验在后端。用curl发一个故意缺字段的请求,看系统是否拦截:
curl -X POST "http://localhost:8080/hengxin/order.do?action=add" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "customerId=1" \ -d "rentalDate=2023-01-01" \ -d "materials=1,2" \ -d "quantities=1,1"预期结果:返回{"success":false,"message":"客户姓名不能为空"}(JSON格式由OrderServlet.java的response.getWriter().write(json)输出)。
失败排查:
- 若返回HTML(如
login.jsp内容),说明Session未建立,检查web.xml里<filter>是否配置了LoginFilter且<url-pattern>覆盖/order.do; - 若返回500错误,看Tomcat日志
logs/catalina.out,大概率是NullPointerException,定位到OrderService.addOrder()里order.getCustomerName()为null时没判空。
5.2 库存联动验证:查stock_log表确认“租出”操作是否触发
创建订单后,系统应自动在stock_log表插入OUT记录。执行SQL:
SELECT * FROM stock_log WHERE operation_type='OUT' ORDER BY operate_time DESC LIMIT 1;预期结果:material_id对应订单里租的器材ID,quantity为租出数量,order_id为刚创建的订单ID。
失败排查:
- 若无记录,检查
OrderService.addOrder()里是否调用了materialDAO.updateStockOut(); - 若
order_id为NULL,检查OrderDAO.insertOrder()是否返回了自增ID并赋值给order.setId(rs.getInt(1)); - 若
operate_time为0000-00-00 00:00:00,说明MySQL的sql_mode包含NO_ZERO_DATE,执行SET sql_mode=(SELECT REPLACE(@@sql_mode,'NO_ZERO_DATE',''));。
5.3 日租金计算验证:修改Material.java的getDailyRent()并热部署
这是检验你能否二次开发的核心。步骤:
- 修改
src/com/hengxin/entity/Material.java,把塔吊日租金逻辑改为:if ("塔吊".equals(this.type)) { return this.rentalDays <= 5 ? 450.0 : 450.0 * 1.3; // 前5天450,超期1.3倍 } - 在MyEclipse里右键项目 →
Refresh,再右键src/→Build Path→Configure Build Path→Source→Allow output folders for source folders→OK; - 右键项目 →
Run As→MyEclipse Server Application,选择Tomcat 6.0; - 访问
http://localhost:8080/hengxin/order_detail.jsp?id=1,看“日租金”是否变为450.0。
关键点:MyEclipse的热部署会自动编译src/下修改的Java文件,无需重启Tomcat。若没生效,检查WEB-INF/classes/com/hengxin/entity/Material.class的时间戳是否更新。
5.4 资金结算导出验证:用financial_settlement.jsp生成Excel
系统提供导出Excel按钮,背后是poi-3.9.jar。验证步骤:
- 在
financial_settlement.jsp里找到<a href="export_excel.jsp">导出Excel</a>; - 访问
http://localhost:8080/hengxin/export_excel.jsp; - 检查浏览器是否下载
settlement_20230101.xls文件; - 用Excel打开,确认A1单元格是“结算单号”,B1是“客户名称”,且数据与页面列表一致。
失败排查:
- 若下载空白文件,检查
export_excel.jsp里response.setContentType("application/vnd.ms-excel;charset=utf-8");的charset=utf-8是否拼错; - 若中文乱码,把
response.setHeader("Content-Disposition", "attachment;filename=settlement.xls");改为response.setHeader("Content-Disposition", "attachment;filename=\"settlement.xls\"");(加英文双引号); - 若报
java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/Workbook,说明poi-3.9.jar没在WEB-INF/lib/下,或版本冲突(删掉poi-3.0.jar等旧版)。
6. 从“能用”到“敢用”:我在3个真实项目里踩过的5个血泪坑
这个系统最珍贵的不是源码,而是它暴露的真实世界接口——那些文档里不会写、但上线第一天就让你头皮发麻的细节。我把它浓缩成5条,每一条都来自血泪教训,现在告诉你怎么绕过去。
6.1 坑1:order_detail.jsp里<img src="upload/<%=order.getPhoto()%>" />的路径黑洞
系统支持上传器材照片,但upload/目录不在WebRoot下,而在D:/hengxin/upload/(硬编码在UploadServlet.java里)。当你在order_detail.jsp看到<img src="upload/123.jpg">,浏览器会向http://localhost:8080/hengxin/upload/123.jpg发起请求,而Tomcat默认不提供D:/hengxin/upload/目录的静态资源服务。现象:图片404。解决:在conf/web.xml里取消注释DefaultServlet的<init-param>:
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <!-- 加这一行 --> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> </servlet>然后在conf/server.xml的<Host>节点里加:
<Context path="/upload" docBase="D:/hengxin/upload" reloadable="true"/>这样/upload/路径就映射到物理目录了。记住:docBase必须是绝对路径,且Tomcat进程要有该目录读取权限。
6.2 坑2:JDBCUtil.java的连接池泄漏,导致第101个请求就卡死
JDBCUtil.getConnection()用的是Druid连接池,但closeConnection()方法里只写了:
public static void closeConnection(Connection conn) { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }问题在于:conn.close()只是把连接归还池,但PreparedStatement和ResultSet没关!当一个订单查询执行SELECT * FROM order WHERE id=?,OrderDAO.getOrderById()里:
PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, id); ResultSet rs = ps.executeQuery(); // 忘记rs.close()和ps.close()结果就是连接池里的连接被ResultSet占用,撑满100个连接后,新请求永远等待。修复:在JDBCUtil.closeConnection()里加参数:
public static void closeConnection(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs != null) try { rs.close(); } catch (SQLException e) {} if (ps != null) try { ps.close(); } catch (SQLException e) {} if (conn != null) try { conn.close(); } catch (SQLException e) {} }所有DAO方法调用后必须JDBCUtil.closeConnection(conn, ps, rs)。这是Java Web开发的铁律,不是这个系统特有,但它的源码里漏了。
6.3 坑3:login.jsp的session.setAttribute("user", user)没设超时,导致账号被共享
AdminServlet.java里登录成功后:
HttpSession session = request.getSession(); session.setAttribute("user", user);但没设session.setMaxInactiveInterval(1800)(30分钟)。后果:用户关掉浏览器,Session还在内存里,别人用同一台电脑开Chrome,session.getAttribute("user")还能拿到管理员对象。修复:在web.xml里加:
<session-config> <session-timeout>30</session-timeout> </session-config>或者在LoginServlet.java里session.setMaxInactiveInterval(1800)。更安全的做法:登录后生成随机token存入user对象,每次请求校验token有效性,token存Redis——但这超出本系统范围,属于架构升级。
6.4 坑4:order_list.jsp的分页SQL在MySQL 5.7+报错
OrderDAO.getOrderList()里分页SQL是:
String sql = "SELECT * FROM order LIMIT " + offset + "," + pageSize;在MySQL 5.7+,order是保留字,必须加反引号:
String sql = "SELECT * FROM `order` LIMIT " + offset + "," + pageSize;否则报You have an error in your SQL syntax。同理,user、group等表名都要加反引号。建议:用MyBatis或JPA替代手写SQL,但当前系统只能手动修。
6.5 坑5:export_excel.jsp导出的Excel在WPS里打开乱码,Office正常
这是因为response.setContentType("application/vnd.ms-excel;charset=utf-8")的charset=utf-8对WPS无效。终极解法:不用response,改用OutputStream直接写:
<% response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=\"settlement.xls\""); OutputStream os = response.getOutputStream(); HSSFWorkbook workbook = new HSSFWorkbook(); // ... 构建workbook workbook.write(os); os.flush(); os.close(); %>去掉charset=utf-8,WPS和Office都能正确识别。这是国产软件兼容性的经典案例。
从那以后我每次接手JSP老系统,第一件事就是grep所有response.setContentType,把charset参数全删掉,改用OutputStream直写。不是偷懒,是让系统在真实世界里活得久一点。希望帮到你。
本文还有配套的精品资源,点击获取