JSP进销存系统源码部署与实战避坑指南 简介这是一套基于Java Web技术实现的JSP进销存管理系统完整源码包面向Java初学者与Web开发入门者解决传统手工管理商品进货、库存与销售过程中易出错、效率低、流程混乱等痛点适用于小型商贸企业或课程设计实践场景。资源共183个文件包含77个JSP页面实现业务交互与展示、12个Java类封装核心业务逻辑、14个GIF与12个JPG/PNG图片界面资源、6个JAR包支撑运行环境、4个XML配置文件如数据库连接与Servlet映射以及PDF/DOCX文档含系统说明与数据库设计整体压缩包仅6.68MB轻量易部署。已有245人学习下载提供可直接运行的完整工程结构——含数据库脚本MDF/LDF文件、前后端代码、编译后的CLASS文件及典型界面资源如speaker.bmp、Imgmenu1_1.bmp等并内置CalendarBean、SpinfoBean等业务实体类与DBResult等数据操作组件便于理解MVC分层逻辑与JDBC集成实践。1. 这不是“老古董复刻”而是用 JSP 快速搭出能跑通、能改、能交差的进销存系统源码包里藏着三类人最需要的东西你搜“jsp进销存管理系统源码数据库文档.rar”点开压缩包第一眼看到的不是报错而是web/WEB-INF/web.xml、src/com/dao/StockDao.java、db/inventory.sql和一份带目录的 Word 文档——这说明它不是教学 Demo而是真实可部署的最小闭环系统。它解决的不是“JSP 还有没有人用”这种哲学问题而是三类人的具体痛点毕业设计学生要两周内跑通答辩演示能解释每行逻辑中小商贸公司老板找外包嫌贵想让懂点 Java 的员工改个字段、加个导出按钮、连上自己那台旧服务器还有刚转 Java Web 的新手需要一个没有 Spring Boot 自动配置干扰、能看清 request → servlet → jdbc → jsp 渲染全链路的标本。这个包的价值不在“多先进”而在“所有环节都暴露在外”数据库建表语句写死在.sql文件里DAO 层没封装成 MyBatis页面用% %直接嵌 Java 表达式连分页都是手写limit ? , ?。它不优雅但每个翻车点都可定位——这才是真·可调试的进销存基线。2. 从解压到首页显示四步走通本地 Tomcat 部署链路2.1 解压后先认清三个核心目录结构及其不可替换性JSP 进销存系统的可运行性高度依赖目录约定不是“把文件丢进 webapps 就行”。解压后你会看到典型三层结构inventory_system/ ├── db/ # 数据库脚本目录关键含建库建表初始数据 │ └── inventory.sql # 注意不是 dump 全库而是 CREATE DATABASE USE CREATE TABLE INSERT ├── src/ # Java 源码Servlet DAO Bean必须编译成 class 放入 WEB-INF/classes │ └── com/ │ └── dao/ │ └── GoodsDao.java ├── web/ # Web 根目录等同于 Tomcat webapps 下的项目名 │ ├── index.jsp # 入口页通常跳转到 login.jsp 或直接展示登录框 │ ├── WEB-INF/ │ │ ├── web.xml # Servlet 映射核心servlet-class 必须与 src 中包路径完全一致 │ │ └── classes/ # 编译后的 .class 文件存放处手动编译后复制至此 │ └── css/js/images/ # 静态资源路径硬编码在 JSP 中挪动会 404 └── doc/ # Word/PDF 文档含 ER 图、功能模块说明、数据库字段注释别跳过字段名和 SQL 不一致是高频坑提示web.xml中servlet-classcom.servlet.LoginServlet/servlet-class对应的是src/com/servlet/LoginServlet.java如果解压后发现src下是com/ctrl/而不是com/servlet/必须同步修改web.xml否则启动即 404。这是新手第一道坎。2.2 手动编译 Java 源码为什么不能靠 IDE 自动构建这个源码包默认不带pom.xml或build.xml意味着它设计为“纯 JDK Tomcat”环境运行。你必须手动编译原因有二一是验证 JDK 版本兼容性JSP 1.2/2.0 对应 JDK 1.4–1.6但此包实测需 JDK 1.8因用了Override和try-with-resources二是确保WEB-INF/classes下的字节码与web.xml中声明的包路径严格匹配。执行以下命令假设 JDK 1.8 已配置 PATHTomcat 为 9.x# 进入 src 目录编译全部 Java 文件-d 指定输出到 web/WEB-INF/classes cd /path/to/inventory_system/src javac -encoding UTF-8 -d ../web/WEB-INF/classes \ com/servlet/*.java \ com/dao/*.java \ com/bean/*.java # 验证编译结果classes 目录下应有 com/servlet/LoginServlet.class 等文件 ls -R ../web/WEB-INF/classes/com/参数说明-encoding UTF-8强制指定源码编码避免中文注释或字符串乱码.java文件若用 GBK 保存此处需改为-encoding GBK-d ../web/WEB-INF/classes输出目录必须精确到classes不能是classes/com否则 Tomcat 加载不到类com/servlet/*.java通配符编译但注意com/servlet/下不能有子包否则需递归编译如com/servlet/user/*.java需单独加行。2.3 数据库初始化MySQL 5.7 下执行 inventory.sql 的三处必改项db/inventory.sql通常包含三类需人工干预的语句类型原始内容示例必改原因修改后示例字符集声明CREATE DATABASE inventory;MySQL 5.7 默认utf8mb4但旧 SQL 可能写utf8仅支持 3 字节CREATE DATABASE inventory CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;时间类型字段create_time datetime新版 MySQL 严格模式下datetime无默认值会报错create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP外键约束FOREIGN KEY (goods_id) REFERENCES goods(id)若goods表建在stock表之后执行顺序错导致外键创建失败在inventory.sql末尾添加SET FOREIGN_KEY_CHECKS 0;开头SET FOREIGN_KEY_CHECKS 1;结尾执行命令确保 MySQL 服务已启动mysql -u root -p /path/to/inventory_system/db/inventory.sql # 输入密码后检查是否成功mysql -u root -p -e USE inventory; SHOW TABLES;2.4 Tomcat 配置server.xml 与 context.xml 的两处关键补丁仅把web目录复制到tomcat/webapps/inventory不够还需修正数据库连接配置。该系统几乎都用JDBC DriverManager直连连接信息藏在web/WEB-INF/web.xml的context-param或某个 Servlet 的init()方法中。但更常见的是硬编码在com/dao/BaseDao.java// com/dao/BaseDao.java 片段需手动修改 private static final String URL jdbc:mysql://localhost:3306/inventory?useUnicodetruecharacterEncodingUTF-8; private static final String USER root; // 注意不是 admin 或 system private static final String PASSWORD 123456; // 生产环境必须改注意如果BaseDao.java中URL含localhost而你的 MySQL 安装在另一台机器必须改成 IP若 MySQL 端口非 3306如阿里云 RDS 常用 33060必须同步改端口。改完需重新编译BaseDao.java并覆盖WEB-INF/classes/com/dao/BaseDao.class。3. 登录后空白页商品列表不加载——五个血泪级避坑指南3.1 现象首页跳转 login.jsp 成功但输入账号密码后页面刷新回 login.jsp控制台无报错原因LoginServlet.java中验证逻辑写成if (user ! null password.equals(user.getPassword()))但数据库user表的password字段是 MD5 加密存储而登录页传入的是明文equals()永远为 false。解决找到LoginServlet.java的doPost方法将密码比对改为// 原错误代码明文比对 if (user ! null password.equals(user.getPassword())) // 改为 MD5 比对需引入 commons-codec 或手写 MD5 工具类 String inputMd5 DigestUtils.md5Hex(password); // 或 new BigInteger(1, MessageDigest.getInstance(MD5).digest(password.getBytes(UTF-8))).toString(16) if (user ! null inputMd5.equals(user.getPassword()))玄学提醒有些包的password字段是SHA-1或base64需查doc/数据库设计说明.docx中“用户表”章节确认加密方式不能猜。3.2 现象登录成功跳转main.jsp但商品列表区域显示javax.servlet.ServletException: java.lang.NoClassDefFoundError: com/mysql/jdbc/Driver原因MySQL JDBC 驱动未放入WEB-INF/lib/。该包通常只提供源码不附带mysql-connector-java-x.x.jar。解决下载mysql-connector-java-5.1.47.jar适配 MySQL 5.7高版本驱动不兼容旧 JSP 写法复制到web/WEB-INF/lib/目录若不存在则新建重启 Tomcat热部署不生效因 ClassLoader 已加载失败。3.3 现象点击“新增商品”弹出层表单提交后提示HTTP Status 405 – Method Not Allowed原因GoodsServlet.java中doPost方法未实现只写了doGet而表单methodpost。解决打开GoodsServlet.java在doGet方法下方添加Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); // 临时方案复用 doGet 逻辑 }血泪经验不要直接删掉doGet改成doPost因为doGet里可能包含request.setAttribute(list, goodsList)用于页面渲染doPost需保留此逻辑。3.4 现象商品列表显示但价格列全是0.0数据库中 price 字段实际有值原因Goods.java实体类中price字段类型为int而数据库price是decimal(10,2)JDBCResultSet.getInt(price)取整导致精度丢失。解决修改com/bean/Goods.java// 原错误定义 private int price; // 改为 private BigDecimal price; // 或 double但 BigDecimal 更准 // 对应 getter/setter 也改为 BigDecimal public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price price; }并在GoodsDao.java的rs.getBigDecimal(price)替换rs.getInt(price)。3.5 现象导出 Excel 功能点击无反应浏览器控制台报ReferenceError: exportExcel is not defined原因exportExcel.js被放在web/js/但list.jsp中引用路径写成script srcjs/exportExcel.js/script而当前页面 URL 是http://localhost:8080/inventory/goods/list.jsp相对路径应为script src../js/exportExcel.js/script。解决统一用绝对路径script src${pageContext.request.contextPath}/js/exportExcel.js/script后悔药所有静态资源路径CSS/JS/图片务必用${pageContext.request.contextPath}开头这是 JSP 最稳妥的上下文根引用方式。4. 把“能跑”变成“能用”三个刚需功能的低成本改造方案4.1 给商品列表加搜索框三步完成模糊查询不用改 DAO 层原系统商品列表通常是全量查SELECT * FROM goods现在要支持按名称搜索。不推荐改GoodsDao.java的 SQL易破坏原有逻辑而是用 Servlet 层拦截在list.jsp表单中增加搜索输入框form actionGoodsServlet methodget input typetext namekeyword placeholder商品名称关键词 value${param.keyword} !-- 保持搜索词回显 -- button typesubmit搜索/button /form修改GoodsServlet.java的doGet方法提取keyword参数并拼接WHERE条件String keyword request.getParameter(keyword); String sql SELECT * FROM goods; if (keyword ! null !keyword.trim().isEmpty()) { sql WHERE name LIKE ?; // 注意PreparedStatement 防注入不能直接拼接 ps conn.prepareStatement(sql); ps.setString(1, % keyword.trim() %); } else { ps conn.prepareStatement(sql); }关键补丁GoodsDao.java的queryAll()方法需重载接受sql和params参数否则无法复用。若原方法无重载直接在GoodsServlet中写完整 JDBC 查询牺牲复用性但最快。4.2 导出 Excel 支持中文表头用 Apache POI 替换老旧 jxl原包导出常使用jxl.jar2009 年停止维护导出中文表头乱码且不支持.xlsx。升级为poi-4.1.2.jar兼容 JDK 1.8删除WEB-INF/lib/jxl.jar下载poi-4.1.2.jar、poi-scratchpad-4.1.2.jar、poi-ooxml-4.1.2.jar、xmlbeans-3.1.0.jar、commons-collections4-4.3.jar五件套放入lib修改ExportExcelServlet.java用XSSFWorkbook替代Workbook// 原 jxl 写法删除 // WritableWorkbook workbook Workbook.createWorkbook(outputStream); // 新 POI 写法 XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(商品列表); // 创建表头行 XSSFRow headerRow sheet.createRow(0); headerRow.createCell(0).setCellValue(商品ID); // 中文无乱码 headerRow.createCell(1).setCellValue(商品名称); // ...其他列设置响应头强制下载response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenamegoods_ System.currentTimeMillis() .xlsx); workbook.write(response.getOutputStream());4.3 权限分级给采购员和销售员分配不同菜单原系统通常只有admin一种角色现需区分purchase只能看入库单、供应商和sales只能看出库单、客户。不碰数据库用 Session 属性控制登录成功后在LoginServlet.java中设置角色// 查询 user 表时多取 role 字段 String role rs.getString(role); // 假设 user 表有 role 字段值为 admin/purchase/sales request.getSession().setAttribute(role, role);在main.jsp的导航栏中用 JSTL 控制菜单显示% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:if test${sessionScope.role admin || sessionScope.role purchase} a hrefinbound/list.jsp入库管理/a /c:if c:if test${sessionScope.role admin || sessionScope.role sales} a hrefoutbound/list.jsp出库管理/a /c:if边界提醒角色字段必须在数据库user表中存在若无则需手动ALTER TABLE user ADD COLUMN role VARCHAR(20) DEFAULT admin;并更新测试账号。5. 验证系统健壮性的四个硬核指标别只看“能登录”5.1 数据一致性验证用 SQL 脚本交叉核对库存变动进销存的核心是“账实相符”。不能只信页面数字要用数据库脚本验证。例如某商品 A 当前库存为 100执行一次“入库 10 件”操作后页面显示 110此时立即执行-- 验证入库单总数量 商品表当前库存 - 初始库存 SELECT (SELECT IFNULL(SUM(num), 0) FROM inbound_item WHERE goods_id 1) AS total_in, (SELECT IFNULL(SUM(num), 0) FROM outbound_item WHERE goods_id 1) AS total_out, (SELECT stock FROM goods WHERE id 1) AS current_stock, ((SELECT IFNULL(SUM(num), 0) FROM inbound_item WHERE goods_id 1) - (SELECT IFNULL(SUM(num), 0) FROM outbound_item WHERE goods_id 1)) AS calc_stock;若current_stock ! calc_stock说明业务逻辑有漏比如入库单保存了但goods.stock字段没更新。此时需检查InboundServlet.java中是否执行了UPDATE goods SET stock stock ? WHERE id ?。5.2 并发安全测试用 Apache Bench 模拟双人同时入库JSP 系统天然缺乏事务隔离高并发下易超卖。用ab工具压测# 模拟 10 个用户各发起 5 次入库请求商品 ID1数量1 ab -n 50 -c 10 http://localhost:8080/inventory/InboundServlet?goodsId1num1观察goods表stock字段变化若初始为 100理论应为 150但实际为 142说明有 8 次请求读到了过期库存值。修复方案在InboundServlet.java的更新 SQL 前加SELECT ... FOR UPDATE或改用UPDATE goods SET stock stock 1 WHERE id 1 AND stock 1乐观锁。5.3 字段长度溢出防护手动触发 varchar(20) 插入 30 字中文数据库goods.name若为varchar(20)插入 30 字中文UTF-8 占 90 字节会截断。测试方法在add.jsp的商品名称输入框粘贴 30 个汉字提交后检查数据库SELECT LENGTH(name), CHAR_LENGTH(name) FROM goods ORDER BY id DESC LIMIT 1;LENGTH()返回字节数应 ≤ 20×360CHAR_LENGTH()返回字符数应 ≤ 20。若CHAR_LENGTH 20说明应用层未校验需在GoodsServlet.java中加String name request.getParameter(name); if (name ! null name.length() 20) { request.setAttribute(error, 商品名称不能超过 20 个字符); request.getRequestDispatcher(add.jsp).forward(request, response); return; }5.4 日志追溯能力在关键业务点埋点记录操作人与时间原系统无操作日志出了问题无法追责。在GoodsServlet.java的doPost新增商品开头加// 记录操作日志到 database_log 表需提前建表 String logSql INSERT INTO database_log (operator, action, target, create_time) VALUES (?, ?, ?, NOW()); PreparedStatement logPs conn.prepareStatement(logSql); logPs.setString(1, (String) request.getSession().getAttribute(username)); // 从 Session 取登录名 logPs.setString(2, add_goods); logPs.setString(3, goods_id goods.getId()); logPs.executeUpdate();我自己的习惯每次交付前我会用 Navicat 连上数据库执行SELECT * FROM database_log ORDER BY create_time DESC LIMIT 10;确认最近 10 条操作都有operator和action。没有这条系统就是黑匣子——出了问题没人知道谁、什么时候、干了什么。希望帮到你。本文还有配套的精品资源点击获取