JSP超市管理系统源码拆解:架构、数据库与部署排错
2026/9/15 3:50:41 网站建设 项目流程

简介:JSP超市管理系统是一套基于浏览器/服务器模式的Java Web项目,采用Java语言与MySQL数据库开发,可在Myeclipse和Tomcat7.0环境中直接部署运行,适合Java Web初学者、课程设计或毕业设计参考。系统功能覆盖五大业务模块:人事管理支持职工信息和供货商信息的查询、添加、删除、修改;销售管理提供销售信息查询与商品销售盘点;进货管理实现进货信息的查询与增删改;库存管理包含商品信息和库存信息的查询及增删改;客户管理支持用户添加、删除和密码修改。各模块结构清晰,便于二次开发。资源以rar压缩包形式提供,大小约1.06MB,内含完整项目源代码、数据库脚本jspsupermarket.sql及数据库配置文件DBO.java,导入开发环境后即可快速搭建运行。目前已有1023人学习下载,系统内置管理员账号admin,可直接登录体验,有助于理解动态网页与数据库的交互、系统分层开发以及基础业务功能的实现。

1. 一套JSP超市管理系统源码能教会你什么

我拆过不少以 JSP + Servlet + MySQL 为底的课程设计源码,这套超市管理系统属于结构很典型、坑也很有代表性的那种。登录页在http://127.0.0.1:8080/supermarket/login.jsp,管理员账号密码都是 admin,数据库脚本是jspsupermarket.sql,系统名直接叫 supermarket。它不像 Spring Boot 项目那样一开始就把 Controller、Service、Mapper 分层铺好,而是把 JSP 页面、Java 类、JDBC 连接全部摊开在面前。正因为没有框架包装,请求如何在浏览器与 Tomcat 之间流转、SQL 如何拼进 PreparedStatement、事务如何用 Connection 手动控制,这些原生的写法都可以直接看到。适合三类人:一是课程设计想快速跑通并改出自己系统的学生;二是想复习 JSP 和原生 JDBC 的 Java 开发;三是需要把老系统翻新成 Spring Boot 的人拿它练手。下面按“架构定位 → 数据建模 → 模块实现 → 部署排错”的顺序把它拆开。

2. 架构与请求流转:JSP页面里的数据是怎么进到MySQL的

2.1 拿到源码后先读目录结构

项目拿到手先不要急着启动,先把 webapps 下的 supermarket 目录结构读明白。这类 MyEclipse 导出的 Web 工程,常见的布局是这样的:WebRoot(有的叫WebContent)下放 JSP 页面,WEB-INF/web.xml是部署描述符,WEB-INF/lib放依赖的 jar 包;Java 源码按包组织,数据库连接代码集中在DBO.java。很多新手会犯一个错:直接把整个 MyEclipse 工程拖进 Tomcat 的 webapps 目录,结果访问 404。正确做法是部署编译后的项目目录,而不是把.java源码文件丢进去。

先看web.xml里有没有配置欢迎页,最常见的写法是这样的:

<welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list>

这段配置的效果是:访问http://127.0.0.1:8080/supermarket/时,Tomcat 会自动帮你跳转到login.jspwelcome-file的查找顺序是从上往下,如果第一个文件不存在,会继续找下一个。前面那段源码没有给出 web.xml 的具体内容,但绝大多数从 MyEclipse 导出的工程都会配这一项,没配的话就只能在地址栏手敲完整路径。

2.2 登录表单提交后请求如何流转

login.jsp的登录表单看整个请求链路是最直观的。这类系统的写法大多是表单直接提交到另一个 JSP 页面,或者提交到一个 LoginServlet。先看表单本身:

<form action="loginCheck.jsp" method="post"> 用户名:<input type="text" name="username"/> 密码:<input type="password" name="password"/> <input type="submit" value="登录"/> </form>

这里action是相对 URL,浏览器会相对于当前页面地址拼接出请求地址;method="post"是为了避免密码出现在地址栏,也绕过了 GET 请求的 URL 长度限制。name属性是前后端约定的参数名,后端靠它取值,如果改了这个值,页面功能会直接断掉。

后端接收参数的 JSP 片段通常长这样:

<% request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); UserDao dao = new UserDao(); User user = dao.findByUsernameAndPassword(username, password); if (user != null) { session.setAttribute("loginUser", user); response.sendRedirect("main.jsp"); } else { out.println("<script>alert('用户名或密码错误');history.back();</script>"); } %>

逻辑上做了三件事:取参数、查库验证、根据结果决定跳转还是回退。request.setCharacterEncoding必须写在取参数之前才会生效,否则中文用户名在 MySQL 5.0 的老环境下很容易变成问号。session.setAttribute把登录用户放进会话,后续页面的权限判断就是从这里读的。sendRedirect是重定向,浏览器地址栏会变成main.jsp;如果改用forward跳转,地址栏不变,刷新时表单会重复提交,这也是老 JSP 系统里最常见的体验问题。

2.3 DBO.java 里集中管理数据库连接

数据库配置文件为什么叫 DBO 而不是 DBUtil?因为这套源码把“数据库对象”的概念集中在一起,所有 DAO 类都通过它拿 Connection。类似系统里配置项基本是四个:驱动类、连接串、用户名、密码。

配置项示例值改错会报什么错
驱动类com.mysql.jdbc.DriverClassNotFoundException
连接串jdbc:mysql://127.0.0.1:3306/supermarketCommunications link failure
用户名rootAccess denied for user
密码和本机 MySQL 一致Access denied for user

连接代码的核心部分一般是这样:

Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection( "jdbc:mysql://127.0.0.1:3306/supermarket?useUnicode=true&characterEncoding=utf8", "root", "123456");

Class.forName的作用是把驱动类加载到 JVM,MySQL 5.0 配套的驱动入口就是com.mysql.jdbc.Driver。如果换到 MySQL 8.x,驱动类要改成com.mysql.cj.jdbc.Driver,连接串里还要追加serverTimezone=Asia/Shanghai,否则会直接抛时区异常。这套源码既然指定了 MySQL 5.0,那就用 5.1.x 版本的mysql-connector-java最稳妥,版本太新反而不一定兼容老语法。

3. 数据库拆解:jspsupermarket.sql与五个业务模块的表结构

3.1 从功能模块反推数据库该有哪些表

把源码功能过一遍就能反推出表结构:人事管理管职工和供货商,销售管理管销售信息盘点,进货管理管进货单,库存管理管商品和库存,客户管理管用户账号。这个库至少要有职工表、供货商表、商品表、进货表、销售表、库存表和用户表,用SHOW TABLES可以一眼确认:

SHOW TABLES; DESC employee;

导入jspsupermarket.sql之后先别急着跑系统,先把表名和注释过一遍。老项目表名前缀往往比较随意,有的是t_employee,有的直接叫emp,对这些细节心里有数,后面写 SQL 时才不会把表名拼错。

3.2 职工表和商品表的字段设计示例

以职工表为例,典型设计是这样的:

CREATE TABLE employee ( emp_id INT NOT NULL AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL, emp_name VARCHAR(50) NOT NULL, dept_name VARCHAR(50) DEFAULT '未分配', phone VARCHAR(20), hire_date DATE, PRIMARY KEY (emp_id), UNIQUE KEY uk_emp_no (emp_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

emp_id是自增主键,新插入数据时不用显式赋值,MySQL 会自动生成;emp_no是工号,一定要加唯一约束,否则录入重复工号会导致数据混乱;DEFAULT '未分配'处理了部门为空的情况。商品表可以参照这个思路设计,但要把价格字段用DECIMAL(10,2)而不是FLOAT,避免浮点精度导致前台显示的金额出现多位小数。

3.3 导入数据库脚本的三种常用方式

命令行导入是兼容性最高的,MySQL 5.0 的客户端直接这样操作:

mysql -u root -p -e "CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8;" mysql -u root -p supermarket < jspsupermarket.sql

第一条命令建库并指定 UTF-8 字符集,第二条把 SQL 脚本导入。这里有个老坑:如果jspsupermarket.sql文件本身是 GBK 编码,而库是 UTF-8,导入后中文注释和中文数据会乱码。Windows 下用 Navicat 导入时,注意在连接属性里把编码切到与脚本一致;Linux 下可以用file jspsupermarket.sql先查看脚本编码再决定建库字符集。MySQL 5.0 默认存储引擎是 MyISAM,但脚本里如果写了ENGINE=InnoDB也没问题,InnoDB 支持事务,对这个系统的进销存业务会更合适。

3.4 销售盘点SQL和索引怎么配合

销售盘点是把商品表和销售明细关联起来按维度汇总,常用 SQL 如下:

SELECT s.sale_date, p.prod_name, SUM(s.quantity) AS total_qty, SUM(s.quantity * s.price) AS total_amount FROM sale_detail s JOIN product p ON s.prod_id = p.prod_id GROUP BY s.sale_date, p.prod_name ORDER BY s.sale_date DESC;

数据量大时这个查询很容易慢,sale_date字段作为过滤和排序条件,给它加个普通索引即可:

ALTER TABLE sale_detail ADD INDEX idx_sale_date (sale_date);

索引不是加得越多越好,像这种进销存系统里,最常作为 WHERE 条件的字段才值得加:销售表的日期、商品表的商品编号、进货表的供货商编号。加了索引后,EXPLAIN SELECT ...能看到key字段命中了idx_sale_date,说明查询走索引而不是全表扫描。

4. 人事/进货/库存模块的CRUD是怎么落地的

4.1 职工列表页:JSP和DAO的最小闭环

职工信息查询页面的套路是固定的:JSP 里写 Java 代码调用 DAO,遍历结果集输出到表格。页面的核心循环代码如下:

<% List<Employee> empList = new EmployeeDao().findAll(); for (Employee e : empList) { %> <tr> <td><%=e.getEmpNo()%></td> <td><%=e.getEmpName()%></td> <td><a href="empEdit.jsp?id=<%=e.getEmpId()%>">修改</a></td> </tr> <% } %>

EmployeeDao().findAll()返回的是封装好的对象列表,比直接操作 ResultSet 可读性好很多;<%=e.getEmpNo()%>是 JSP 表达式,等价于out.print(e.getEmpNo())。需要注意for循环的大括号必须闭合在%><%之间,漏掉闭合会导致整个页面语法错误。

DAO 层的查询方法,常见写法是这样:

public List<Employee> findAll() { String sql = "SELECT * FROM employee ORDER BY emp_id"; try (Connection conn = DBO.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { List<Employee> list = new ArrayList<>(); while (rs.next()) { Employee e = new Employee(); e.setEmpId(rs.getInt("emp_id")); e.setEmpName(rs.getString("emp_name")); list.add(e); } return list; } }

try-with-resources语法会在 try 块结束后自动关闭 Connection、PreparedStatement、ResultSet,避免老代码里忘关连接导致连接池耗尽。但要注意 MyEclipse 8.5 里默认编译级别要调到 1.7 以上,否则这种语法直接编译不过。rs.getIntrs.getString的参数是列名,如果 SQL 里用了SELECT emp_id AS id,这里就要用别名。

4.2 增加和修改:request.getParameter到SQL参数的映射

进货和职工模块的新增表单在 JSP 里是普通 HTML input,controller 部分把参数取出来再塞进 INSERT 语句:

String empName = request.getParameter("empName"); String empNo = request.getParameter("empNo"); String phone = request.getParameter("phone"); String sql = "INSERT INTO employee(emp_no, emp_name, phone) VALUES(?,?,?)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, empNo); ps.setString(2, empName); ps.setString(3, phone); ps.executeUpdate();

这里用?占位符而不是直接拼接字符串,第一个?对应emp_no,第二个对应emp_name,第三个对应phone,顺序必须和 SQL 字段顺序一致。一旦参数顺序写反,数据就会错位,比如手机号存到姓名里。这种方式也避免了 SQL 注入:用户输入里如果带上了单引号,setString会把它当作普通字符处理,而不是结束字符串再执行一段新的 SQL。

修改操作和新增区别不大,SQL 换成 UPDATE 并带上主键条件。这里有个值得检查的点:老系统里很多用 POST 提交表单,但没有判断提交来源,外部直接构造 POST 请求就能改数据。如果只想做教学演示,这个漏洞可以不动,但如果是真实商用,必须加 session 权限校验。

4.3 删除操作里那个最容易被忽略的坑

删除功能的写法看起来最简单:

String id = request.getParameter("id"); String sql = "DELETE FROM employee WHERE emp_id=" + id;

这行代码如果直接用,参数稍微伪造一下就能把整张表清空。正确的做法是用?占位:

String sql = "DELETE FROM employee WHERE emp_id=?"; ps.setInt(1, Integer.parseInt(id));

setInt的第二个参数要求必须是数值类型,所以先用Integer.parseInt做类型校验,这本身就给非法输入加了一道关卡。生产环境中商品和职工删除还应该检查是否存在关联记录,比如商品被采购过就不能直接删,否则销售盘点时 JOIN 会产生空数据。

4.4 进货和库存联动:事务把两张表绑在一起

进货单插入和库存更新是两个操作,必须放在同一个事务里。常见的事务控制骨架如下:

conn.setAutoCommit(false); try { String insertSql = "INSERT INTO purchase(purchase_no, prod_id, quantity, purchase_date) VALUES(?,?,?,?)"; // 执行进货单插入 String updateSql = "UPDATE product SET stock = stock + ? WHERE prod_id = ?"; // 执行库存累加 conn.commit(); } catch (Exception e) { conn.rollback(); throw e; }

conn.setAutoCommit(false)表示关闭自动提交,之后执行的 SQL 只有在commit()时才真正生效。如果进货单插入成功但库存更新失败,会走rollback()把前面的插入也回滚掉,保证两张表的数据永远一致。这套源码里的 DBO.java 默认不会开启事务,需要自己在业务代码里控制,这也是拆这种老项目时最容易看到破绽的地方。

5. 部署到Tomcat 7.0验证,顺便把JSP编译后的class找出来

5.1 MyEclipse 8.5里完成部署

先把 Tomcat 7.0 配置到 MyEclipse 的 Servers 窗口,然后在工程上右键选择 Deploy 或直接将编译后的工程目录放到apache-tomcat-7.0.x/webapps下面。启动 Tomcat 后访问http://127.0.0.1:8080/supermarket/login.jsp,用 admin/admin 登录。如果 404,优先检查 webapps 里是不是真的多了 supermarket 这个目录,以及目录下是不是有WEB-INF

5.2 首次访问时JSP被编译成了什么

有一个问题很多人不知道答案:jsp 编译后的 class 文件保存在哪里。Tomcat 对 JSP 的处理不是直接读文件,而是先把 JSP 翻译成 Java 源文件,再编译成 class。位置在:

# Linux/macOS find /opt/tomcat7/work -name "*login_jsp*" # Windows # 在 Tomcat 安装目录的 work\Catalina\localhost\supermarket\org\apache\jsp\ 下

work目录下可以看到login_jsp.javalogin_jsp.class。打开login_jsp.java,里面_jspService方法对应的就是页面主体,out.write输出的就是 HTML 片段。改完 JSP 发现页面没变化时,删掉 work 对应目录再重启 Tomcat,强制重新编译,这是最直接的排查手段。

5.3 启动报错对照与排查顺序

报错信息原因处理办法
ClassNotFoundException: com.mysql.jdbc.DriverMySQL 驱动 jar 没放进 WEB-INF/lib下载mysql-connector-java-5.1.x.jar拷入 lib
Communications link failureMySQL 服务没启动或端口不通确认 3306 端口监听,netstat -an查看
Access denied for user 'root'@'localhost'DBO.java 里的密码与 MySQL 实际密码不一致修改 DBO.java 后重新编译
HTTP 404项目部署路径不对检查 webapps 目录和访问 URL 大小写

最后提一个验证技巧:改数据库配置文件 DBO.java 后,必须在 MyEclipse 里重新编译并重启 Tomcat,因为源码改动不经过重新编译就不会生效;而改 JSP 只需要清理 work 目录就够了,这一条能帮你省掉大半无关紧要的重启时间。

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

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

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

立即咨询