简介: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.jsp。welcome-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.Driver | ClassNotFoundException |
| 连接串 | jdbc:mysql://127.0.0.1:3306/supermarket | Communications link failure |
| 用户名 | root | Access 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.getInt和rs.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.java和login_jsp.class。打开login_jsp.java,里面_jspService方法对应的就是页面主体,out.write输出的就是 HTML 片段。改完 JSP 发现页面没变化时,删掉 work 对应目录再重启 Tomcat,强制重新编译,这是最直接的排查手段。
5.3 启动报错对照与排查顺序
| 报错信息 | 原因 | 处理办法 |
|---|---|---|
ClassNotFoundException: com.mysql.jdbc.Driver | MySQL 驱动 jar 没放进 WEB-INF/lib | 下载mysql-connector-java-5.1.x.jar拷入 lib |
Communications link failure | MySQL 服务没启动或端口不通 | 确认 3306 端口监听,netstat -an查看 |
Access denied for user 'root'@'localhost' | DBO.java 里的密码与 MySQL 实际密码不一致 | 修改 DBO.java 后重新编译 |
| HTTP 404 | 项目部署路径不对 | 检查 webapps 目录和访问 URL 大小写 |
最后提一个验证技巧:改数据库配置文件 DBO.java 后,必须在 MyEclipse 里重新编译并重启 Tomcat,因为源码改动不经过重新编译就不会生效;而改 JSP 只需要清理 work 目录就够了,这一条能帮你省掉大半无关紧要的重启时间。
本文还有配套的精品资源,点击获取