☰
Java进销存管理系统毕设实战:JSP+SQL Server从环境搭建到事务一致性
2026/10/12 13:40:13 网站建设 项目流程

简介:这份资源是面向高校计算机专业学生与Java Web初学者的进销存管理系统完整毕设方案,基于JSP表现层与MSSQL数据库实现,采用表现层、业务逻辑层、数据访问层的三层架构,通过JDBC完成数据交互。系统覆盖商品管理、供应商管理、进货管理、销售管理、库存预警与报表分析等核心模块,可作为课程设计、毕业设计或Java Web入门练手项目。压缩包共163个文件,约1.7MB,以105个class编译文件与31个java源码为主,另含jar依赖、png界面截图、mdf与ldf数据库文件、xls数据表及doc毕设文档,源码与文档配套齐全。目前已有249人学习下载。读者可从中获取可运行的完整工程、数据库文件与毕业设计文档,通过阅读JSP页面、Servlet与JavaBean的调用关系,理解前端请求如何关联后端逻辑并操作数据库,同时借助需求分析、系统设计与测试评价等内容,掌握Java Web项目从设计到部署的完整流程,提升编程与数据库管理能力。

1. 从一份毕设压缩包说起:jsp+mssql 的进销存系统到底能跑多远

很多做课程设计或毕业设计的同学,第一次拿到「java进销存管理系统的设计与实现(jsp+mssql)包含源代码+毕设.zip」这类资源时,第一反应是解压、找 main 方法、点运行。结果往往卡在数据库连不上、Tomcat 版本对不上、页面 404 这三件事上。这个标题背后其实是一套非常典型的技术栈组合:JSP 做视图层、Servlet/Javabean 做控制与业务、SQL Server 做数据存储,整体跑在 Tomcat 容器里。它解决的是中小型商贸企业最朴素的诉求——把采购、销售、库存三条线的数据串起来,让库存数量不再是 Excel 里那个永远对不上的数字。适合谁?适合需要一套能讲清楚分层架构、能演示完整增删改查、能在答辩时画出 E-R 图的同学,也适合想用最低学习成本搭一个内部台账工具的小团队。但我要先把话说在前面:这套组合能跑通业务,但离生产级还有距离,后面几章我会把距离具体量化给你看。

2. 环境与数据库:把 mssql 和 Tomcat 的版本坑先填平

2.1 为什么这套组合偏爱 SQL Server 而不是 MySQL

标题里写的是 mssql,不是 mysql,这不是笔误。SQL Server 在 Windows 环境下和 JSP 的配合有历史惯性:早期很多教材和实验室机器预装的就是 SQL Server,图形化管理工具 SSMS 对新手友好,建库建表可视化程度高。但代价是 JDBC 驱动版本必须和数据库版本严格对应。常见做法是:SQL Server 2008/2012 配 sqljdbc4.jar,2016 以上配 mssql-jdbc-7.x 或更高。如果你拿的是老源码,里面引用的驱动类名可能是com.microsoft.sqlserver.jdbc.SQLServerDriver,这个类名在新旧驱动里都兼容,但连接串参数差异很大。我一般会先确认三件事:数据库实例名、是否启用 TCP/IP 协议、端口是不是默认 1433。很多连不上的问题,根源是 SQL Server 配置管理器里 TCP/IP 没启用,而不是代码写错了。

2.2 建库脚本与连接串的最小可用配置

假设你已经装好 SQL Server,先用 SSMS 建一个库,名字随意,比如jxc_db。然后执行建表语句。下面这段是进销存最核心的三张表,我把它简化到能跑通业务的最小集合:

-- 商品表:进销存的基础档案 CREATE TABLE product ( pid INT IDENTITY(1,1) PRIMARY KEY, -- 自增主键 pname NVARCHAR(100) NOT NULL, -- 商品名称 price DECIMAL(10,2) DEFAULT 0, -- 单价 stock INT DEFAULT 0 -- 当前库存 ); -- 采购表:记录进货动作 CREATE TABLE purchase ( id INT IDENTITY(1,1) PRIMARY KEY, pid INT NOT NULL, -- 关联商品 qty INT NOT NULL, -- 采购数量 pdate DATETIME DEFAULT GETDATE() -- 采购时间 ); -- 销售表:记录出货动作 CREATE TABLE sale ( id INT IDENTITY(1,1) PRIMARY KEY, pid INT NOT NULL, qty INT NOT NULL, sdate DATETIME DEFAULT GETDATE() );

逻辑说明:商品表存静态档案和动态库存,采购表和销售表只存流水,库存的增减通过业务代码在插入流水后同步更新 product.stock。参数上,IDENTITY(1,1)是 SQL Server 的自增写法,和 MySQL 的 AUTO_INCREMENT 不同,迁移时要注意。NVARCHAR支持中文,别用 VARCHAR 存商品名,否则乱码会让你查半天。

连接串我一般写成这样,放在一个单独的db.properties里,不要硬编码在 JSP 里:

driver=com.microsoft.sqlserver.jdbc.SQLServerDriver url=jdbc:sqlserver://127.0.0.1:1433;databaseName=jxc_db;encrypt=false user=sa password=你的密码

注意encrypt=false这个参数,新版驱动默认开启加密,老环境没配证书会直接报 SSL 错误,加上它能省掉一个经典翻车点。

2.3 在 Tomcat 里跑通第一个 JSP 页面

把项目放进webapps目录,启动 Tomcat,访问http://localhost:8080/项目名/index.jsp。如果 404,先看web.xml里的 welcome-file 配置,再看 JSP 文件是不是放在了 WEB-INF 下面——放 WEB-INF 里是访问不到的,这是新手最容易踩的坑。如果报 500 且堆栈指向驱动类找不到,说明sqljdbc的 jar 没放进WEB-INF/lib,别只放在 Tomcat 的 lib 里,项目移植时会丢。

3. 采购、销售、库存三条线的代码落地

3.1 用 JSP+Servlet 把采购入库写成可复现的闭环

进销存的核心不是页面多漂亮,而是「入库加库存、出库减库存」这个动作不能错。我一般把业务逻辑放在 Servlet 里,JSP 只负责展示。下面是一个采购入库的 Servlet 核心片段:

// PurchaseServlet.java 核心逻辑 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int pid = Integer.parseInt(req.getParameter("pid")); int qty = Integer.parseInt(req.getParameter("qty")); Connection conn = null; PreparedStatement ps1 = null, ps2 = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务,两步操作要么都成功要么都回滚 // 第一步:插入采购流水 ps1 = conn.prepareStatement( "INSERT INTO purchase(pid, qty) VALUES(?, ?)"); ps1.setInt(1, pid); ps1.setInt(2, qty); ps1.executeUpdate(); // 第二步:增加商品库存 ps2 = conn.prepareStatement( "UPDATE product SET stock = stock + ? WHERE pid = ?"); ps2.setInt(1, qty); ps2.setInt(2, pid); ps2.executeUpdate(); conn.commit(); // 两步都成功才提交 } catch (Exception e) { if (conn != null) conn.rollback(); // 任何一步失败,整体回滚 throw new ServletException(e); } finally { DBUtil.close(conn, ps1, ps2); } }

逻辑说明:这里的关键是setAutoCommit(false)和commit/rollback的配对。采购入库涉及两张表的写操作,如果只写了流水没加库存,或者加了库存没写流水,数据就脏了。参数上,qty必须做正整数校验,否则有人传负数进来,库存反而被扣减,这是业务逻辑漏洞,不是技术漏洞,但杀伤力一样大。销售出库同理,只是把stock + ?改成stock - ?,并且要在更新前先查一次库存够不够,不够就抛异常回滚。

3.2 库存查询与分页:别让全表扫描拖垮演示

演示环境数据量小,怎么写都快。但答辩老师如果问「数据量大了怎么办」,你得有话说。库存查询最常见的写法是SELECT * FROM product,然后 JSP 里 for 循环。我一般会加上分页,用 SQL Server 的OFFSET FETCH:

-- 第 pageNo 页,每页 pageSize 条 SELECT pid, pname, price, stock FROM product ORDER BY pid OFFSET (? - 1) * ? ROWS FETCH NEXT ? ROWS ONLY;

参数说明:第一个?是页码,第二个?是每页条数,第三个?也是每页条数。SQL Server 2012 以上才支持这个语法,更老的版本要用ROW_NUMBER()嵌套子查询。如果你拿到的源码用的是后者,别急着改,先确认数据库版本。分页的意义不只是性能,更是让你在答辩时能讲出「我考虑了数据量增长」这句话。

3.3 用触发器还是用代码维护库存一致性

有些源码会在 SQL Server 里写触发器,采购表插入后自动更新库存。这样做的好处是业务代码简单,坏处是逻辑分散在数据库里,调试时像看黑匣子。我一般倾向于在 Java 代码里用事务控制,原因有三:一是触发器出错时堆栈不直观,二是触发器嵌套调用容易死锁,三是代码里的事务边界更清晰,方便加日志。如果你接手的是触发器版本,先别删,把触发器的逻辑读一遍,确认它和代码里的更新没有重复执行,否则库存会翻倍。这个坑我见过不止一次,现象是采购一次库存加了两倍,查半天代码没毛病,最后发现触发器也在干活。

4. 避坑与排查:那些让演示当场翻车的细节

4.1 中文乱码:从 JSP 到数据库的三层编码

现象:页面输入「测试商品」,存进数据库变成问号,或者页面显示乱码。原因:JSP 页面编码、请求编码、数据库字段编码三者不一致。解决:JSP 头部加<%@ page contentType="text/html;charset=UTF-8" %>,Servlet 里在取参数前加req.setCharacterEncoding("UTF-8"),数据库字段用 NVARCHAR,连接串不加额外编码参数。三层都对齐 UTF-8,基本能解决。如果还有问题,检查 Tomcat 的server.xml里 Connector 有没有URIEncoding="UTF-8"。

4.2 连接泄漏:演示到一半数据库连不上了

现象:反复刷新页面几次后,报「连接池已满」或直接超时。原因:Connection、PreparedStatement、ResultSet 没有在 finally 里关闭。解决:写一个 DBUtil 工具类,把关闭逻辑封装成静态方法,每个 Servlet 的 finally 里调用。别指望 JSP 里用<% %>脚本段关闭,那种写法迟早漏。参数上,如果用的是 Tomcat 自带的连接池,在context.xml里配maxActive和maxIdle,别用默认值,默认值往往偏小。

4.3 主键冲突:自增列和手动插入混用

现象:新增商品时报「违反主键约束」。原因:建表时用了 IDENTITY 自增,但插入语句里又手动指定了 pid 值,或者从旧数据迁移时把自增值重置错了。解决:自增列永远不要在 INSERT 里显式赋值,迁移数据后用DBCC CHECKIDENT('product', RESEED, 最大pid)重置种子。这个命令是 SQL Server 特有的,MySQL 里对应的是ALTER TABLE ... AUTO_INCREMENT。

4.4 事务没回滚:库存扣成负数还在跑

现象:销售出库时库存不够,但流水已经写进去了。原因:先插流水后查库存,或者查库存和扣库存不在同一个事务里。解决:把「查库存」和「扣库存」放在同一个事务中,用SELECT ... FOR UPDATE锁行,或者直接在 UPDATE 语句里加条件WHERE stock >= ?,根据返回的受影响行数判断是否成功。后者更简单,推荐。

4.5 Tomcat 版本与 JDK 版本不匹配

现象:项目在别人机器上能跑,到你这里启动就报UnsupportedClassVersionError。原因:编译用的 JDK 版本高于运行时的 JRE 版本。解决:统一 JDK 和 Tomcat 的版本,老项目一般用 JDK 8 + Tomcat 8 或 9,别上来就 JDK 17,Servlet 的javax包名在新版本里已经变成jakarta,改起来不是一行两行的事。

5. 从能跑到能讲:把毕设变成面试里的加分项

这套 jsp+mssql 的进销存系统,如果只是跑起来交差,价值有限。我一般会建议多做三件事,让它从「课程作业」变成「能聊的技术项目」。第一,把库存变更的 SQL 抽出来,加一层简单的日志表,记录每次变更前后的数量、操作时间、操作类型。这样答辩时你能讲「我做了数据可追溯」,面试时也能聊「审计日志的设计」。第二,把分页查询的 SQL 换成存储过程,虽然 JSP 里调用存储过程稍麻烦,但能让你讲清楚「预编译」和「SQL 注入防护」的关系。第三,写一个最简单的压力测试脚本,用 Java 起 50 个线程并发调用采购接口,观察库存最终值是否等于预期值。如果不等,说明事务隔离级别有问题,这就是你深入聊READ COMMITTED和REPEATABLE READ的切入点。

// 并发测试片段:50 个线程各采购 1 件,最终库存应增加 50 ExecutorService pool = Executors.newFixedThreadPool(50); for (int i = 0; i < 50; i++) { pool.submit(() -> { // 调用采购接口,pid=1,qty=1 purchaseService.buy(1, 1); }); } pool.shutdown(); pool.awaitTermination(1, TimeUnit.MINUTES); // 查询 product 表 pid=1 的 stock,对比预期值

参数说明:线程数不要超过数据库连接池上限,否则测的是连接池不是事务。预期值 = 初始库存 + 50,如果小于这个数,说明有更新丢失,需要检查事务隔离级别或者加锁策略。这个测试不需要多高深,但能让你在答辩时多讲五分钟,而且讲的是真东西。

最后说个我自己的习惯:每次拿到这类源码包,先不急着改代码,而是把数据库建好、连接串配好、跑通一个最简单的查询页面。这一步通了,后面都是体力活;这一步不通,后面全是玄学。希望帮到你。

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

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

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

立即咨询