☰
JSP+MySQL进销存管理系统:部署、架构与避坑指南
2026/10/15 6:57:01 网站建设 项目流程

简介:基于JSP与MySQL的进销存管理系统,是一套面向Java Web学习者的完整项目,适合高校课程设计、毕业设计及中小企业进销存业务信息化参考。系统围绕商品、订单、库存、报表四条主线,实现了商品信息增删改查、订单提交与状态跟踪、库存预警及销售统计等典型功能。技术层面采用JSP动态网页配合Servlet处理请求,通过JDBC连接MySQL数据库,运用MVC模式分离界面、业务与数据访问,并引入自定义标签库简化页面逻辑,同时以连接池优化资源占用,配合用户认证与权限控制保障数据安全。压缩包约630KB,为zip格式,内含项目源码与配套数据库文件,便于本地部署运行。已有199人学习。对于希望掌握JSP与MySQL开发流程的读者,这份资源提供了从数据库表设计、后台逻辑编写到前端页面展示的完整参考,可直接用于二次开发或作为项目模板。

1. 什么是 JSP+MySQL 进销存管理系统:一个 zip 背后的完整技术栈

“JSP+MySql进销存管理系统有数据库.zip”这个标题,单看像是一个课程设计或毕业设计打包好的产物——前端用 JSP 页面,后端用 Servlet 做控制层,数据库用 MySQL,压缩包里还附带了建库脚本和初始化数据。它要解决的场景非常具体:商品信息的增删改查、采购入库、销售出库、库存流水,再加上一点简单的统计报表,正好是中小型公司内部管理后台最典型的需求。适合谁呢?一类是刚学完 Java Web 想找个完整项目练手的学生,另一类是想快速搭一套内部系统、不想一上来就上 Spring Boot 全家桶的开发者。这个结构不算复杂,但五脏俱全——JSP + Servlet + JDBC + MySQL 是 Java Web 最经典的一套组合,把它彻底跑通搞懂,后面再换 Spring Boot 也只是换了层皮,业务逻辑和数据库设计的思路完全通用。

很多人拿到这个压缩包的第一反应是直接解压、导入 IDE、点运行,然后被各种版本的坑卡住。这个项目年代感强,用的多半是 JDK 8、Tomcat 8 甚至更老的 Servlet 规范,MySQL 驱动也可能停留在 5.x 时代。你的电脑上如果装的是 MySQL 8 和最新版 Tomcat,光是把环境对齐就得花掉半天。这正是我写这篇文章的原因——从 zip 里有什么开始,到部署、改代码、避坑,一条线走完,让你拿到这个包能真正跑起来,而不是解压完就扔在桌面。

2. 拆开 zip 看工程:JSP+Servlet+DAO 三层结构里到底有什么

2.1 源码目录里必须有这几样,缺了就是残缺包

下载这种进销存系统的压缩包,第一步不是双击运行,而是先看目录结构是否完整。一个规范的 JSP+Servlet 项目解压后应该有这几个核心部分:src 源码目录、WebContent(也可能是 webapp)目录、以及打包好的 war 文件或可部署的目录结构。src 里面按包名分层,常见的是 entity(实体类)、dao(数据访问层)、servlet(控制器层)、util(工具类)这四个包,有些做得细的还会拆出 service 层。

jxc-system/ ├── src/ │ ├── com/jxc/entity/ # 实体类:Goods, Supplier, Customer, User │ ├── com/jxc/dao/ # 数据访问层:GoodsDao, StockDao, SaleDao │ ├── com/jxc/servlet/ # 控制器层:GoodsServlet, StockServlet │ └── com/jxc/util/ # DBUtil.java 数据库连接工具类 ├── WebContent/ │ ├── admin/ # 后台管理页面:goods_list.jsp, stock_in.jsp │ ├── css/ js/ # 静态资源 │ ├── WEB-INF/ │ │ ├── web.xml # Servlet 映射、欢迎页面配置 │ │ └── lib/ # JDBC 驱动、JSTL 标签库 │ └── index.jsp # 登录页或首页 └── database/ └── jxc.sql # 建库建表脚本 + 初始数据

这个结构看一眼就能判断项目的质量。如果只有源码没有 database 目录下的 SQL 脚本,那这个包基本是残废的——JSP+Servlet 项目的灵魂在数据库脚本里,表结构不清楚,代码根本没法跑。如果 WEB-INF/lib 下没有 mysql-connector 的 jar 包,说明依赖得自己补,后面部署的时候会多一个坑。

2.2 数据库脚本:整个系统的命脉

打开 jxc.sql,重点看三样东西:建库语句、表结构、初始数据。规范的做法是CREATE DATABASE jxc_db CHARACTER SET utf8;开头,然后是USE jxc_db;,再往下是各张表的CREATE TABLE。进销存核心表至少要有商品表、供应商表、客户表、入库单表、出库单表、库存表这么几张。

-- 商品表 CREATE TABLE `goods` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL COMMENT '商品名称', `spec` VARCHAR(50) DEFAULT NULL COMMENT '规格型号', `price_in` DECIMAL(10,2) DEFAULT NULL COMMENT '进货价', `price_out` DECIMAL(10,2) DEFAULT NULL COMMENT '销售价', `stock` INT(11) DEFAULT '0' COMMENT '库存数量', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这里注意两点。第一,表名和字段名是不是和实体类对得上——Goods.java里的属性应该和goods表的列一一对应,get/set方法名也必须驼峰对应,比如数据库里是price_in,Java 里就是priceIn。第二,看初始数据有没有。很多课程设计为了演示效果,会插入几十条商品数据和几条用户记录,这些数据在第一次部署时就是你的调试样本。

2.3 从 web.xml 到页面跳转:一次完整的请求路径

要在这个系统里改功能,先搞清楚一次请求是怎么走的。用户在浏览器输入地址,请求先到 web.xml 里配置的 Servlet 映射,Servlet 收到请求后调用 DAO 层查数据库,拿到结果后forward或redirect到 JSP 页面渲染。web.xml 是这里的枢纽。

<servlet> <servlet-name>GoodsServlet</servlet-name> <servlet-class>com.jxc.servlet.GoodsServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>GoodsServlet</servlet-name> <url-pattern>/goods</url-pattern> </servlet-mapping>

这个配置的意思是:浏览器访问http://localhost:8080/jxc/goods时,请求会被 Tomcat 交给GoodsServlet处理。<servlet-name>只是别名,真正干活的是<servlet-class>指向的那个 Java 类。如果你拿到项目后发现改了 url-pattern 不生效,多半是没重启 Tomcat 或者配置写在了多个 XML 里造成冲突。

3. 在本地把系统跑起来:JDK、Tomcat 与 MySQL 的版本组合

3.1 三个环境的版本匹配清单

这类老项目的环境组合是最劝退的。我花过一整个下午在 MySQL 8 和 Tomcat 10 的组合上折腾一个 JSP 项目,最后发现 Tomcat 10 把javax.servlet换成了jakarta.servlet,整个项目的 import 全部报错。这不是代码问题,是版本断代问题。最稳的组合是 JDK 8 + Tomcat 8.5 + MySQL 5.7,或者 JDK 8 + Tomcat 9 + MySQL 5.7。你本机如果已经装了 MySQL 8,也不是不能用,但要注意两点:驱动要用mysql-connector-java-8.0.x.jar,且连接 URL 里要加serverTimezone=Asia/Shanghai,否则会报时区错误。

JDK 版本不建议低于 8,高于 8 也行——但有些老代码在 JDK 11 上会报模块访问错误,比如java.xml.bind模块被移除。如果本机只有 JDK 17,建议直接装一个 JDK 8 专门跑这种项目,把 IDE 的编译等级锁定在 1.8。

组件推荐版本注意事项
JDK1.8.0_2xx不要用 17/21 跑老项目
Tomcat8.5.x 或 9.0.x别用 10.x,javax 变 jakarta
MySQL5.7 或 8.08.0 要配新驱动和时区参数
IDEEclipse / IDEA选对部署方式,原生 JSP 项目转 Maven 会拆骨架

3.2 导入数据库的两种方式

数据库是整个系统的底盘,导入成功等于完成了一半。推荐用命令行导入,不依赖图形工具的菜单位置。在 Windows 下打开 cmd,切到database目录,执行下面的命令:

mysql -u root -p < jxc.sql

回车后输入密码,如果没有报错,说明脚本执行成功。接着用下面这条命令验证表是否建好:

mysql -u root -p -e "USE jxc_db; SHOW TABLES;"

如果你用 MySQL 8,导入脚本时容易遇到一个报错:Unknown collation: 'utf8_general_ci'或者Specified key was too long。前者是脚本里写的老排序规则在 8.0 里的差异,后者是 utf8mb4 在索引长度上的限制。最简单的方法是新建数据库时指定字符集,或是直接改用可视化工具导入。某公司给客户部署时就是被这个卡住,最后发现是客户机器上装的 MySQL 8 和脚本里写的utf8_general_ci不兼容,手动把脚本里的字符集改掉才通过。

图形工具导入就简单了——用 Navicat 或 MySQL Workbench 建库,然后右键运行 SQL 文件,选到 jxc.sql 执行。注意如果脚本里没有CREATE DATABASE和USE语句,你得先手动建一个库,再在查询器顶部写上USE jxc_db;再执行文件内容,否则表会建错库。

3.3 部署 war 包与启动排查

项目如果是打包好的 war 文件,部署在 Tomcat 上非常简单。把 jxc.war 复制到 Tomcat 的webapps目录,启动 Tomcat,它会自动解压并部署。如果是源码工程,在 IDE 里配置 Tomcat 运行时,然后把项目加到 Server 上启动。启动后访问http://localhost:8080/项目名/,看到登录页就说明部署成功。

如果启动时报错,先看 Tomcat 的logs/catalina.out(Linux)或logs/catalina.<日期>.log(Windows),重点找Caused by后面的内容。常见的无非是数据库连不上(检查 URL、用户名、密码)、驱动类找不到(检查 jar 是否在 WEB-INF/lib)、端口被占(8080 被别的进程占了,改 Tomcat 的 server.xml 里<Connector port="8080">为其他端口)。这些排错思路比到处搜日志更高效。

4. 改代码前先读懂这三个核心模块:DBUtil、DAO 与报表 SQL

4.1 数据库连接工具类与 JDBC 驱动加载

进销存系统的数据访问代码一般集中在 dao 包里,但所有 DAO 类都会用到一个公共的工具类——DBUtil。这个类通常长这样:

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/jxc_db?useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

注意第 7 行,Class.forName加载的是com.mysql.jdbc.Driver。如果你用的是 MySQL 8 的驱动包,这个类名需要改成com.mysql.cj.jdbc.Driver。这就是为什么很多人换驱动 jar 后反而报ClassNotFoundException——驱动包新版里,老的类名仍然存在但不再是默认加载入口。URL 里的serverTimezone=Asia/Shanghai是 MySQL 8 必须加的,5.7 不加也没事,但加了也不会有副作用。改动这套配置时,密码如果变了,只改这里一处即可。

4.2 商品管理:从 DAO 到 JSP 的数据流

在 JSP+Servlet 项目里,数据流是单向的:JSP 页面发起请求,Servlet 接收请求并调用 DAO,DAO 执行 SQL 并返回结果集,Servlet 把结果放到request作用域里,转发回 JSP 渲染。拿商品列表这个功能来说,DAO 层的方法大致是这样:

public List<Goods> findAll() { List<Goods> list = new ArrayList<>(); String sql = "SELECT id, name, spec, price_in, price_out, stock FROM goods"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Goods g = new Goods(); g.setId(rs.getInt("id")); g.setName(rs.getString("name")); g.setSpec(rs.getString("spec")); g.setPriceIn(rs.getBigDecimal("price_in")); g.setPriceOut(rs.getBigDecimal("price_out")); g.setStock(rs.getInt("stock")); list.add(g); } } catch (SQLException e) { e.printStackTrace(); } return list; }

这个方法用了 JDK 7 的 try-with-resources 语法,Connection、PreparedStatement、ResultSet都会自动关闭,不需要手动写 finally 里的一堆close()。但你要注意,老项目里很多代码不是这么写的,用的是conn = DBUtil.getConnection();然后 finally 里一个个关,漏关一个就是连接泄漏。改造方向是无脑替换成 try-with-resources。

参数方面,PreparedStatement用?占位符传参,比字符串拼接 SQL 安全得多。原项目里如果还有"SELECT * FROM goods WHERE name='" + name + "'"这种写法,属于 SQL 注入的活靶子,二次开发时首先得清理这些位置。

4.3 进销存报表:SQL 聚合与分页

进销存系统里最有含金量的功能是统计报表,常见的有:按日期的销售统计、库存预警、毛利汇总。这些查询用 SQL 聚合函数实现,比在 Java 里写循环快一个数量级。做一张简单的“销售汇总表”通常长这样:

SELECT DATE_FORMAT(sale_date, '%Y-%m-%d') AS sale_day, SUM(quantity) AS total_qty, SUM(amount) AS total_amount FROM sale_records WHERE sale_date BETWEEN ? AND ? GROUP BY sale_day ORDER BY sale_day DESC;

DATE_FORMAT是 MySQL 的时间格式化函数,负责把时间戳按天截断,这是按日报表最常用的手段。BETWEEN ? AND ?是报表日期范围参数,两个占位符分别接收开始和结束日期。GROUP BY sale_day把同一天的多条销售记录合并成一行,SUM把数量和金额汇总。

分页又是另一个高频需求。JSP 老项目里分页常用的做法是LIMIT offset, rows:

int page = Integer.parseInt(request.getParameter("page")); int pageSize = 10; int offset = (page - 1) * pageSize; String sql = "SELECT * FROM goods LIMIT ?, ?";

页面底部渲染页码时,还需要一条总记录数的查询SELECT COUNT(*) FROM goods,然后用总记录数除以 pageSize 算出总页数。这里有个细节,LIMIT后面用?占位符时,offset是 int 类型,但有些 MySQL 驱动对LIMIT ?, ?的 PreparedStatement 类型转换很敏感,需要全部用setInt而不建议用setString。

5. 部署与二次开发避坑:现象、原因与解决

5.1 页面中文乱码,POST 和 GET 各有一个坑

现象:商品列表页能打开,但数据库里的中文全部变成??,或者页面显示乱码。

原因有两个层面。一是数据库连接 URL 没指定字符集,二是页面请求和响应编码不一致。DBUtil的 URL 里没有characterEncoding=utf8时,JDBC 连接默认按系统编码传输,中文在 MySQL 和 Java 之间来回转换就丢了。另外,老项目的 JSP 页面往往在文件头写了pageEncoding="UTF-8",但浏览器提交表单时用的是ISO-8859-1,POST 请求的中文到 Servlet 里就乱掉了。

解决:URL 里加参数,同时在 Servlet 的 doPost 方法里手动设置编码。

String url = "jdbc:mysql://localhost:3306/jxc_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; // 在 Servlet 中,必须在读取参数之前调用 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8");

5.2 JDBC 驱动加载失败,ClassNotFoundException 和 NoClassDefFoundError

现象:Tomcat 启动时控制台报ClassNotFoundException: com.mysql.jdbc.Driver,或者运行时第一次查数据库就抛异常。

原因:mysql 驱动的 jar 包没有放进WEB-INF/lib目录,或者放到了 IDE 的 classpath 里但没有被 Tomcat 加载。JSP+Servlet 项目不像 Maven 项目那样自动拉依赖,jar 包必须实实在在地复制到WebContent/WEB-INF/lib下,重新部署才生效。

解决:打开WEB-INF/lib目录,确认存在mysql-connector-java-*.jar。如果没有,从本地 Maven 仓库里找到对应 jar(或者去官网下载 5.1.49 或 8.0.33 版本),复制进去,然后右键项目点 Refresh,再重启 Tomcat。

5.3 MySQL 8 与旧驱动的时间戳报错

现象:项目用的是 MySQL 8,但配置的还是老驱动 5.1.x,报错信息类似The server time zone value '�й���ʱ��' is unrecognized。

原因:MySQL 8 的默认时区配置在驱动看来是无效值,5.1 驱动不会自动校准连接时区。

解决:换用mysql-connector-java-8.0.x.jar,并把驱动类名改成com.mysql.cj.jdbc.Driver,URL 里添加serverTimezone=Asia/Shanghai。这组合在 MySQL 8 上是最稳的。

5.4 Tomcat 部署路径导致 404

现象:Tomcat 启动没有报错,访问http://localhost:8080/jxc/出现 404。

原因:Tomcat 的 webapps 目录下,项目实际解压后的文件名和你访问的 URL 路径不一致。比如 war 包叫jxc.war,解压后目录是jxc,但你访问的是/jxcsys。

解决:先查看webapps目录下实际文件夹名,然后访问与文件夹名一致的路径。或者直接把目录改名成你想用的项目名,改完重启 Tomcat。我一般习惯把项目名统一为/jxc,免得前端页面里写死的链接全部失效。

5.5 数据库连接泄漏导致“too many connections”

现象:系统跑一两天后,操作页面越来越慢,最后数据库报Too many connections。

原因:DAO 层的方法在某条分支里没有关闭Connection或ResultSet。最常见的是查询结果为空时直接return,跳过了 finally 里的关闭代码。这种教科书级别的坑,在老项目里反而更常见。

解决:把所有 DAO 方法统一改成 try-with-resources 写法,调试阶段可以在 DBUtil 里加一个计数器打印连接数:

public static Connection getConnection() throws SQLException { connectionCount++; System.out.println("当前连接数: " + connectionCount); return DriverManager.getConnection(URL, USER, PASSWORD); }

每执行一次查询看到计数只增不减,基本可以断定有连接没关。逐个方法替换完,再把打印去掉。

6. 从能跑到能用:把课程设计改成生产系统的几个习惯

这个 zip 里的系统能跑通,只是第一步。真要给公司内部用或者当成毕设答辩的加分项,至少有四件事值得做。第一,把 DBUtil 里的硬编码配置挪到jdbc.properties文件里,再用Properties类加载,这样换环境不用改代码重新编译。第二,把DriverManager换成BasicDataSource连接池,处理并发访问时连接数能被复用而不是不断新建销毁。第三,在 Servlet 层加一个统一的字符编码过滤器,所有请求和响应都走 UTF-8,不用在每个 Servlet 里重复写setCharacterEncoding。第四,也是我最建议的一个习惯——把业务逻辑从 Servlet 里拆到 Service 层,至少让入库和出库操作带上事务控制。

// 在 B/S 架构的进销存系统里,事务必须放在 Service 层 // 不要在 DAO 层里分别提交,否则前半段入库成功、后半段扣库存失败,数据就烂了 public void doStockIn(StockRecord record) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启手动事务 stockInDao.insert(conn, record); goodsDao.updateStock(conn, record.getGoodsId(), record.getQuantity()); conn.commit(); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw new RuntimeException("入库失败,事务回滚", e); } finally { if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

这段代码的关键点:conn.setAutoCommit(false)之后,所有 SQL 都不会立即生效,要等conn.commit()才统一提交。如果中间任何一步抛异常,rollback()会把已经执行的 SQL 全部撤销。finally里把autoCommit恢复成true是为了连接回到连接池时状态干净,避免影响下一次使用。这也是老项目代码里几乎见不到的写法,学会这一手,整个系统的数据安全性就上一个台阶。

密码存储方面,课程设计里通常是把明文密码存在 user 表里,甚至写死在 JSP 页面。稳妥做法是至少加一层 MD5 加盐,用DigestUtils.md5Hex(password + salt)生成摘要,数据库里只存摘要不存明文。虽然不算最先进,但对于这种体量的系统已经比旁边那些明文存储的同事靠谱多了。

最后说个自己的习惯。每次拿到这种老项目,我第一件事不是跑代码,而是先用文本检索工具搜一遍password、select *和DriverManager,把这三个关键词命中的位置全部列出来审视一遍。这三类代码改完,项目才从“演示能跑”变成“上线敢用”。这个流程我走了很多次,希望帮到你。

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

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

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

立即咨询