Java Web超市管理系统实战:半小时搭建完整项目,掌握CRUD与三层架构
大家好,我是CSDN的一名技术博主。很多Java初学者在学完基础语法和Web开发后,常常苦于找不到一个合适的项目来串联知识点,将理论转化为实战能力。超市管理系统就是一个经典的练手项目,它涵盖了从数据库设计、后端逻辑到前端展示的完整流程,非常适合用来巩固Java Web技术栈。本文将手把手带你,从零开始,在半小时内快速搭建一个功能完整的超市管理系统,并提供完整的源码和清晰的实现思路,让你不仅“做得出来”,更能“理解透彻”。
1. 项目背景与核心价值
超市管理系统是一个典型的信息管理系统(MIS),其核心目标是实现对超市日常运营中商品、员工、供应商、销售等核心业务数据的信息化、规范化管理。对于开发者而言,这类项目具有极高的学习价值:
- 技术栈全面 :它几乎覆盖了Java Web开发的所有基础环节:Servlet/JSP(或Spring MVC)、JDBC(或MyBatis)、MySQL数据库、HTML/CSS/JavaScript前端、Tomcat服务器等。
- 业务逻辑典型 :包含了对数据的 增删改查 (CRUD)操作,这是所有业务系统的基石。你将实际处理表单提交、数据验证、数据库交互、结果展示等一系列连贯操作。
- 贴近实际应用 :虽然作为学习项目进行了简化,但其模块划分(如商品管理、员工管理、销售统计)与实际商业系统高度相似,有助于你建立业务建模的思维。
通过完成这个项目,你将能系统性地掌握如何将一个业务需求,分解为数据库表结构,再通过Java代码实现后台逻辑,最终用网页呈现给用户的完整开发流程。这远比孤立地学习某个框架或语法更有意义。
2. 环境准备与项目结构
在开始编码前,我们需要准备好“战场”。请确保你的开发环境包含以下组件,版本无需完全一致,但建议使用主流稳定版以避免兼容性问题。
2.1 开发环境清单
- 操作系统 :Windows 10/11, macOS 或 Linux 均可。
-
Java开发工具包(JDK)
:版本 8 或 11(推荐JDK 8,兼容性最广)。安装后请配置好
JAVA_HOME环境变量。 - 集成开发环境(IDE) :Eclipse IDE for Enterprise Java Developers 或 IntelliJ IDEA(社区版即可)。本文演示以 Eclipse 为主,IDEA操作逻辑类似。
- Web服务器 :Apache Tomcat 9.x。下载后解压,并在Eclipse中配置好Server Runtime。
- 数据库 :MySQL 5.7 或 8.0。你需要安装MySQL服务器,并准备好一个图形化管理工具,如 MySQL Workbench 或 Navicat 。
-
数据库驱动
:MySQL Connector/J(即JDBC驱动)的JAR包(如
mysql-connector-java-8.0.xx.jar)。
2.2 创建动态Web项目
-
打开Eclipse,选择
File -> New -> Dynamic Web Project。 -
输入项目名称,例如
SupermarketManagementSystem。 - Target runtime 选择你已配置好的Apache Tomcat 9.0。
- Dynamic web module version 选择 3.1 或 4.0(取决于你的Tomcat版本,Tomcat 9支持3.1/4.0)。
-
勾选
Generate web.xml deployment descriptor(这将为我们生成关键的web.xml配置文件)。 -
点击
Finish完成创建。
2.3 项目目录结构说明
创建完成后,你的项目结构应类似于以下形式。清晰的结构是项目可维护性的第一步。
SupermarketManagementSystem/
├── src/ # Java源代码目录
│ └── (你的包路径,如 com.supermarket.dao, com.supermarket.model等)
├── WebContent/ # Web资源目录 (Eclipse Dynamic Web Project标准)
│ ├── META-INF/
│ ├── WEB-INF/
│ │ ├── lib/ # 存放第三方JAR包,如MySQL驱动
│ │ └── web.xml # Web应用部署描述文件
│ ├── css/ # 样式表文件
│ ├── js/ | JavaScript文件
│ ├── images/ # 图片资源
│ └── *.jsp # JSP页面文件(如 index.jsp, goodsList.jsp)
└── build/classes/ # 编译后的class文件(Eclipse自动管理)
关键一步
:将下载好的
mysql-connector-java-xxx.jar
文件,复制到
WebContent/WEB-INF/lib/
目录下。这样项目在部署时才能正确加载数据库驱动。
3. 数据库设计与实现
任何管理系统的核心都是数据。我们先设计数据库,这是整个项目的“地基”。
3.1 数据库概念模型
我们设计四个核心表,满足基本的管理需求:
- 商品表 (goods) :存储商品信息。
- 员工表 (employee) :存储系统操作员信息。
- 供应商表 (supplier) :存储商品供应商信息。
- 销售记录表 (sale_record) :存储每一笔销售流水。
3.2 SQL建表语句
在你的MySQL数据库中,创建一个名为
supermarket_db
的数据库,然后执行以下SQL语句。
-- 创建数据库
CREATE DATABASE IF NOT EXISTS supermarket_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE supermarket_db;
-- 1. 商品表
CREATE TABLE goods (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID',
goods_name VARCHAR(100) NOT NULL COMMENT '商品名称',
price DECIMAL(10, 2) NOT NULL COMMENT '单价',
stock INT NOT NULL DEFAULT 0 COMMENT '库存数量',
supplier_id INT COMMENT '供应商ID',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_supplier (supplier_id),
INDEX idx_name (goods_name)
) COMMENT='商品信息表';
-- 2. 供应商表
CREATE TABLE supplier (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '供应商ID',
supplier_name VARCHAR(100) NOT NULL COMMENT '供应商名称',
contact_person VARCHAR(50) COMMENT '联系人',
phone VARCHAR(20) COMMENT '联系电话',
address VARCHAR(200) COMMENT '地址',
INDEX idx_name (supplier_name)
) COMMENT='供应商信息表';
-- 3. 员工表(系统用户)
CREATE TABLE employee (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录用户名',
password VARCHAR(100) NOT NULL COMMENT '登录密码(存储加密后的)',
real_name VARCHAR(50) NOT NULL COMMENT '真实姓名',
role VARCHAR(20) DEFAULT 'staff' COMMENT '角色:admin(管理员), staff(员工)',
INDEX idx_username (username)
) COMMENT='员工/用户表';
-- 4. 销售记录表
CREATE TABLE sale_record (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '记录ID',
goods_id INT NOT NULL COMMENT '商品ID',
quantity INT NOT NULL COMMENT '销售数量',
total_price DECIMAL(10, 2) NOT NULL COMMENT '销售总价',
employee_id INT NOT NULL COMMENT '操作员ID',
sale_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '销售时间',
INDEX idx_goods (goods_id),
INDEX idx_employee (employee_id),
INDEX idx_time (sale_time)
) COMMENT='销售记录表';
-- 添加外键约束(确保数据完整性)
ALTER TABLE goods ADD CONSTRAINT fk_goods_supplier FOREIGN KEY (supplier_id) REFERENCES supplier(id) ON DELETE SET NULL;
ALTER TABLE sale_record ADD CONSTRAINT fk_sale_goods FOREIGN KEY (goods_id) REFERENCES goods(id) ON DELETE CASCADE;
ALTER TABLE sale_record ADD CONSTRAINT fk_sale_employee FOREIGN KEY (employee_id) REFERENCES employee(id);
-- 插入初始测试数据
INSERT INTO employee (username, password, real_name, role) VALUES
('admin', MD5('123456'), '系统管理员', 'admin'),
('zhangsan', MD5('123456'), '张三', 'staff');
INSERT INTO supplier (supplier_name, contact_person, phone, address) VALUES
('农夫山泉股份有限公司', '李经理', '13800138001', '浙江省杭州市西湖区'),
('康师傅控股有限公司', '王主任', '13900139002', '天津市经济技术开发区');
INSERT INTO goods (goods_name, price, stock, supplier_id) VALUES
('农夫山泉550ml', 2.00, 100, 1),
('康师傅红烧牛肉面', 4.50, 80, 2),
('可口可乐330ml', 3.00, 150, NULL);
设计要点说明 :
-
主键与自增
:每个表都有一个
id作为主键,并设置为自增 (AUTO_INCREMENT),方便管理和关联。 -
字段注释
:使用
COMMENT为每个字段添加注释,这在团队协作和后期维护时非常有用。 -
索引优化
:对经常用于查询条件的字段(如
goods_name,username,sale_time)创建了索引 (INDEX),可以大幅提升查询速度。 -
外键约束
:通过
FOREIGN KEY建立了表之间的关联,保证了数据的一致性和完整性。例如,不能销售一个不存在的商品。 -
密码加密
:在插入员工数据时,我们使用了MySQL的
MD5()函数对密码进行简单的加密存储。 注意 :在生产环境中,应使用更安全的加密方式,如BCrypt。 -
字符集
:使用
utf8mb4字符集,以支持存储Emoji等所有Unicode字符。
4. 核心功能模块实现(Java Web三层架构)
我们将采用经典的 JSP + Servlet + JavaBean (Model) 模式,也可以理解为一种简化的三层架构:表示层(JSP)、控制层(Servlet)、模型层(JavaBean和DAO)。
4.1 模型层 (Model) - 实体类与数据库工具
首先创建与数据库表对应的Java实体类(JavaBean)。
1. 数据库连接工具类 (DBUtil.java) 这个类负责加载驱动、获取连接、释放资源,是所有数据库操作的基础。
// 文件路径:src/com/supermarket/util/DBUtil.java
package com.supermarket.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtil {
private static final String URL = "jdbc:mysql://localhost:3306/supermarket_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
private static final String USER = "root"; // 你的数据库用户名
private static final String PASSWORD = "yourpassword"; // 你的数据库密码
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver"); // 加载驱动
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("找不到数据库驱动!");
}
}
// 获取数据库连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
// 关闭资源
public static void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2. 商品实体类 (Goods.java)
// 文件路径:src/com/supermarket/model/Goods.java
package com.supermarket.model;
import java.math.BigDecimal;
import java.util.Date;
public class Goods {
private Integer id;
private String goodsName;
private BigDecimal price; // 使用BigDecimal处理金额,避免精度丢失
private Integer stock;
private Integer supplierId;
private Date createTime;
// 关联的供应商名称(非数据库字段,用于页面显示)
private String supplierName;
// 无参构造器、全参构造器、Getter和Setter方法
public Goods() {}
public Goods(Integer id, String goodsName, BigDecimal price, Integer stock, Integer supplierId, Date createTime) {
this.id = id;
this.goodsName = goodsName;
this.price = price;
this.stock = stock;
this.supplierId = supplierId;
this.createTime = createTime;
}
// 此处省略所有属性的 getter 和 setter 方法,实际开发中请使用IDE生成。
// 例如:public Integer getId() { return id; } public void setId(Integer id) { this.id = id; }
}
按照同样的模式,创建
Employee.java
,
Supplier.java
,
SaleRecord.java
等实体类。
4.2 数据访问层 (DAO) - 商品管理示例
DAO层封装了所有对数据库的操作。这里以商品管理为例。
// 文件路径:src/com/supermarket/dao/GoodsDao.java
package com.supermarket.dao;
import com.supermarket.model.Goods;
import com.supermarket.util.DBUtil;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class GoodsDao {
// 1. 查询所有商品(带供应商名称)
public List<Goods> getAllGoods() {
List<Goods> list = new ArrayList<>();
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
String sql = "SELECT g.*, s.supplier_name FROM goods g LEFT JOIN supplier s ON g.supplier_id = s.id ORDER BY g.id DESC";
try {
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
while (rs.next()) {
Goods goods = new Goods();
goods.setId(rs.getInt("id"));
goods.setGoodsName(rs.getString("goods_name"));
goods.setPrice(rs.getBigDecimal("price"));
goods.setStock(rs.getInt("stock"));
goods.setSupplierId(rs.getInt("supplier_id"));
goods.setCreateTime(rs.getTimestamp("create_time"));
goods.setSupplierName(rs.getString("supplier_name")); // 设置关联的供应商名
list.add(goods);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn, pstmt, rs);
}
return list;
}
// 2. 根据ID查询单个商品
public Goods getGoodsById(int id) {
Goods goods = null;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
String sql = "SELECT * FROM goods WHERE id = ?";
try {
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
rs = pstmt.executeQuery();
if (rs.next()) {
goods = new Goods();
goods.setId(rs.getInt("id"));
goods.setGoodsName(rs.getString("goods_name"));
goods.setPrice(rs.getBigDecimal("price"));
goods.setStock(rs.getInt("stock"));
goods.setSupplierId(rs.getInt("supplier_id"));
goods.setCreateTime(rs.getTimestamp("create_time"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn, pstmt, rs);
}
return goods;
}
// 3. 新增商品
public boolean addGoods(Goods goods) {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "INSERT INTO goods(goods_name, price, stock, supplier_id) VALUES(?, ?, ?, ?)";
try {
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, goods.getGoodsName());
pstmt.setBigDecimal(2, goods.getPrice());
pstmt.setInt(3, goods.getStock());
if (goods.getSupplierId() != null) {
pstmt.setInt(4, goods.getSupplierId());
} else {
pstmt.setNull(4, Types.INTEGER);
}
int rows = pstmt.executeUpdate();
return rows > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
} finally {
DBUtil.close(conn, pstmt, null);
}
}
// 4. 更新商品信息
public boolean updateGoods(Goods goods) {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "UPDATE goods SET goods_name=?, price=?, stock=?, supplier_id=? WHERE id=?";
try {
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, goods.getGoodsName());
pstmt.setBigDecimal(2, goods.getPrice());
pstmt.setInt(3, goods.getStock());
if (goods.getSupplierId() != null) {
pstmt.setInt(4, goods.getSupplierId());
} else {
pstmt.setNull(4, Types.INTEGER);
}
pstmt.setInt(5, goods.getId());
int rows = pstmt.executeUpdate();
return rows > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
} finally {
DBUtil.close(conn, pstmt, null);
}
}
// 5. 根据ID删除商品
public boolean deleteGoods(int id) {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "DELETE FROM goods WHERE id = ?";
try {
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
int rows = pstmt.executeUpdate();
return rows > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
} finally {
DBUtil.close(conn, pstmt, null);
}
}
}
4.3 控制层 (Servlet) - 商品列表与添加
Servlet作为控制器,接收页面请求,调用DAO处理业务,并转发到对应的JSP页面。
1. 商品列表展示Servlet (GoodsListServlet.java)
// 文件路径:src/com/supermarket/servlet/GoodsListServlet.java
package com.supermarket.servlet;
import com.supermarket.dao.GoodsDao;
import com.supermarket.model.Goods;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@WebServlet("/goodsList") // 定义访问路径
public class GoodsListServlet extends HttpServlet {
private GoodsDao goodsDao = new GoodsDao();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 调用DAO获取数据
List<Goods> goodsList = goodsDao.getAllGoods();
// 2. 将数据存入request作用域,供JSP页面使用
request.setAttribute("goodsList", goodsList);
// 3. 转发到商品列表JSP页面
request.getRequestDispatcher("/goodsList.jsp").forward(request, response);
}
}
2. 添加商品Servlet (AddGoodsServlet.java)
// 文件路径:src/com/supermarket/servlet/AddGoodsServlet.java
package com.supermarket.servlet;
import com.supermarket.dao.GoodsDao;
import com.supermarket.model.Goods;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
@WebServlet("/addGoods")
public class AddGoodsServlet extends HttpServlet {
private GoodsDao goodsDao = new GoodsDao();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置请求编码,防止中文乱码
request.setCharacterEncoding("UTF-8");
// 2. 获取表单参数
String name = request.getParameter("goodsName");
String priceStr = request.getParameter("price");
String stockStr = request.getParameter("stock");
String supplierIdStr = request.getParameter("supplierId");
// 3. 参数校验(简单示例)
if (name == null || name.trim().isEmpty()) {
request.setAttribute("errorMsg", "商品名称不能为空!");
request.getRequestDispatcher("/addGoods.jsp").forward(request, response);
return;
}
// 4. 封装数据到Goods对象
Goods goods = new Goods();
goods.setGoodsName(name.trim());
try {
goods.setPrice(new BigDecimal(priceStr));
goods.setStock(Integer.parseInt(stockStr));
if (supplierIdStr != null && !supplierIdStr.isEmpty()) {
goods.setSupplierId(Integer.parseInt(supplierIdStr));
}
} catch (NumberFormatException e) {
request.setAttribute("errorMsg", "价格或库存格式错误!");
request.getRequestDispatcher("/addGoods.jsp").forward(request, response);
return;
}
// 5. 调用DAO执行插入
boolean success = goodsDao.addGoods(goods);
if (success) {
// 添加成功,重定向到列表页(防止表单重复提交)
response.sendRedirect(request.getContextPath() + "/goodsList");
} else {
request.setAttribute("errorMsg", "添加商品失败,请重试!");
request.getRequestDispatcher("/addGoods.jsp").forward(request, response);
}
}
}
4.4 表示层 (JSP) - 商品列表页面
JSP页面负责数据的展示和用户交互。
商品列表页面 (goodsList.jsp)
<%-- 文件路径:WebContent/goodsList.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>商品管理</title>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body>
<div class="container">
<h1>商品列表</h1>
<a href="addGoods.jsp" class="btn">添加新商品</a>
<table border="1" cellspacing="0" width="100%">
<tr>
<th>ID</th>
<th>商品名称</th>
<th>单价</th>
<th>库存</th>
<th>供应商</th>
<th>创建时间</th>
<th>操作</th>
</tr>
<%-- 使用JSTL遍历request中传来的goodsList --%>
<c:forEach var="goods" items="${requestScope.goodsList}">
<tr>
<td>${goods.id}</td>
<td>${goods.goodsName}</td>
<td>¥${goods.price}</td>
<td>${goods.stock}</td>
<td>${goods.supplierName}</td>
<td>${goods.createTime}</td>
<td>
<a href="editGoods.jsp?id=${goods.id}">编辑</a> |
<a href="javascript:if(confirm('确定删除吗?')) location.href='deleteGoods?id=${goods.id}'">删除</a>
</td>
</tr>
</c:forEach>
</table>
</div>
</body>
</html>
添加商品页面 (addGoods.jsp)
<%-- 文件路径:WebContent/addGoods.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加商品</title>
</head>
<body>
<h2>添加新商品</h2>
<%-- 显示错误信息 --%>
<c:if test="${not empty errorMsg}">
<p style="color:red;">${errorMsg}</p>
</c:if>
<form action="addGoods" method="post">
<label>商品名称:</label><input type="text" name="goodsName" required><br><br>
<label>单价:</label><input type="number" step="0.01" name="price" required><br><br>
<label>库存:</label><input type="number" name="stock" required><br><br>
<label>供应商:</label>
<select name="supplierId">
<option value="">--请选择--</option>
<%-- 这里应该从数据库动态加载供应商列表,为简化示例,先写死 --%>
<option value="1">农夫山泉股份有限公司</option>
<option value="2">康师傅控股有限公司</option>
</select><br><br>
<input type="submit" value="提交">
<input type="button" value="返回" onclick="history.back()">
</form>
</body>
</html>
4.5 配置与运行
-
配置
web.xml:虽然我们使用了@WebServlet注解,但web.xml中仍需配置欢迎页和字符编码过滤器(解决POST请求乱码)。<!-- 文件路径:WebContent/WEB-INF/web.xml --> <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 欢迎页面 --> <welcome-file-list> <welcome-file>login.jsp</welcome-file> <!-- 可以先做一个登录页 --> </welcome-file-list> <!-- 字符编码过滤器 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> -
添加JSTL支持
:为了在JSP中使用
<c:forEach>等标签,需要将JSTL的JAR包(如javax.servlet.jsp.jstl.jar和javax.servlet.jsp.jstl-api.jar)也放入WEB-INF/lib目录。 -
部署运行
:在Eclipse中,右键项目 ->
Run As->Run on Server,选择配置好的Tomcat服务器。访问http://localhost:8080/SupermarketManagementSystem/goodsList即可看到商品列表。
5. 功能扩展与模块完善
按照上述商品管理的模式,你可以举一反三,完成其他模块:
- 员工管理模块 :实现员工登录(Session管理)、员工信息的增删改查。登录时,将用户输入的密码进行MD5加密后与数据库存储的加密密码比对。
- 供应商管理模块 :独立的CRUD操作。
- 销售管理模块 :这是业务核心。销售时,需要先查询商品库存,足够则生成销售记录,并 同步更新商品库存 。 这里必须使用数据库事务来保证“扣减库存”和“生成记录”两个操作要么都成功,要么都失败 ,防止数据不一致。
- 统计报表模块 :编写复杂的SQL语句,实现按日、按月统计销售额,或统计畅销商品。
6. 常见问题与排查思路
在开发过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 404错误:页面找不到 |
1. URL路径错误。
2. Servlet未正确配置
@WebServlet
或
web.xml
。
3. 项目未成功部署到Tomcat。 |
1. 检查浏览器地址栏URL与Servlet注解路径是否一致。
2. 检查Eclipse的Servers视图,确认项目已发布。 3. 清理Tomcat工作目录并重新发布。 |
| 500错误:服务器内部错误 |
1. JSP/Servlet代码有语法或逻辑错误。
2. 数据库连接失败。 3. 空指针异常(NPE)。 |
1. 查看Tomcat控制台(Console)输出的完整异常堆栈信息,这是最重要的线索。
2. 检查
DBUtil.java
中的数据库URL、用户名、密码是否正确。
3. 检查MySQL服务是否启动。 |
| 插入中文到数据库显示乱码 | 数据库、连接、表字段的字符集不统一。 |
1. 确保数据库、表、字段的字符集为
utf8mb4
。
2. 确保JDBC连接URL中包含
characterEncoding=utf8
。
3. 确保JSP页面和Servlet请求/响应编码设置为
UTF-8
。
|
JSP页面无法解析
<c:forEach>
标签
| 未导入JSTL标签库或缺少JAR包。 |
1. 检查JSP页面头部是否有
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
。
2. 检查
WEB-INF/lib
下是否有JSTL相关的JAR包。
|
| 表单提交后,Servlet获取的参数为null |
1. 表单
input
的
name
属性与Servlet中
request.getParameter()
的参数名不匹配。
2. 表单提交方式(GET/POST)与Servlet处理的方法(doGet/doPost)不匹配。 |
1. 仔细核对表单字段的
name
和Servlet中获取的参数名。
2. 表单是
method="post"
,则Servlet必须重写
doPost
方法。
|
| 删除或更新操作影响多行数据 |
SQL语句的WHERE条件不精确,通常是因为忘记指定主键
id
。
| 这是严重错误! 在执行DELETE或UPDATE前,务必在控制台打印出最终要执行的SQL语句,确认WHERE条件能唯一锁定一行数据。 |
7. 项目优化与最佳实践建议
完成基础功能后,可以从以下方面提升项目质量,这更贴近企业级开发:
-
引入Maven/Gradle进行依赖管理
:手动管理JAR包非常繁琐。使用Maven,只需在
pom.xml中声明依赖(如Servlet API、JSTL、MySQL驱动、连接池等),构建工具会自动下载和管理,极大提升开发效率。 -
使用数据库连接池
:像上面
DBUtil那样每次操作都新建连接,性能极差。应使用如 HikariCP , Druid 等连接池。在Maven项目中引入依赖,并配置数据源。 -
采用成熟框架
:
- 控制层 :用 Spring MVC 替代原生Servlet,注解驱动更简洁。
- 数据层 :用 MyBatis 或 Spring Data JPA 替代原生JDBC,能自动处理参数映射、结果集映射,减少大量模板代码。
- 视图层 :可以考虑使用 Thymeleaf 等现代模板引擎替代JSP,它们与Spring Boot集成更好,功能更强大。
- 实现分层解耦 :在DAO和Servlet之间引入 Service(业务逻辑层) 。Servlet只负责接收请求和响应,具体的业务逻辑(如销售时的库存检查、事务管理)放在Service层,使代码结构更清晰,职责更明确。
-
增强安全性
:
- 密码加密 :不要使用MD5,应使用 BCryptPasswordEncoder 等加盐哈希算法。
-
SQL注入防护
:坚持使用
PreparedStatement(我们已用),切勿拼接SQL字符串。 -
XSS防护
:对用户输入进行过滤或转义,或在JSP中使用
<c:out value="${value}" />输出。 - 会话管理 :对需要登录的页面,在Servlet或过滤器中检查Session中是否存在用户信息。
- 加入日志 :使用 SLF4J + Logback 记录系统运行日志、错误日志和操作日志,便于线上问题排查。
- 前端优化 :使用 Bootstrap 等CSS框架快速构建美观的响应式界面。使用 jQuery 或 Vue.js 实现更流畅的交互,如异步加载数据、表单验证等。
这个超市管理系统项目麻雀虽小,五脏俱全。从环境搭建、数据库设计、到后端逻辑编写、前端页面展示,你完整地走通了一个Java Web应用的生命周期。理解了这个流程,你再学习Spring Boot等高级框架时,就会明白它们本质上是在简化这个流程中的各个步骤。建议你不仅完成代码复制,更要尝试自己添加“销售统计”、“用户权限控制”等功能,遇到问题就去搜索、调试,这才是能力提升的关键。项目的完整源码可以在文末的链接中获取,希望能为你后续的学习和面试带来实实在在的帮助。
更多推荐


所有评论(0)