大家好,我是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项目

  1. 打开Eclipse,选择 File -> New -> Dynamic Web Project
  2. 输入项目名称,例如 SupermarketManagementSystem
  3. Target runtime 选择你已配置好的Apache Tomcat 9.0。
  4. Dynamic web module version 选择 3.1 或 4.0(取决于你的Tomcat版本,Tomcat 9支持3.1/4.0)。
  5. 勾选 Generate web.xml deployment descriptor (这将为我们生成关键的 web.xml 配置文件)。
  6. 点击 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 数据库概念模型

我们设计四个核心表,满足基本的管理需求:

  1. 商品表 (goods) :存储商品信息。
  2. 员工表 (employee) :存储系统操作员信息。
  3. 供应商表 (supplier) :存储商品供应商信息。
  4. 销售记录表 (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 配置与运行

  1. 配置 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>
    
  2. 添加JSTL支持 :为了在JSP中使用 <c:forEach> 等标签,需要将JSTL的JAR包(如 javax.servlet.jsp.jstl.jar javax.servlet.jsp.jstl-api.jar )也放入 WEB-INF/lib 目录。
  3. 部署运行 :在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. 项目优化与最佳实践建议

完成基础功能后,可以从以下方面提升项目质量,这更贴近企业级开发:

  1. 引入Maven/Gradle进行依赖管理 :手动管理JAR包非常繁琐。使用Maven,只需在 pom.xml 中声明依赖(如Servlet API、JSTL、MySQL驱动、连接池等),构建工具会自动下载和管理,极大提升开发效率。
  2. 使用数据库连接池 :像上面 DBUtil 那样每次操作都新建连接,性能极差。应使用如 HikariCP , Druid 等连接池。在Maven项目中引入依赖,并配置数据源。
  3. 采用成熟框架
    • 控制层 :用 Spring MVC 替代原生Servlet,注解驱动更简洁。
    • 数据层 :用 MyBatis Spring Data JPA 替代原生JDBC,能自动处理参数映射、结果集映射,减少大量模板代码。
    • 视图层 :可以考虑使用 Thymeleaf 等现代模板引擎替代JSP,它们与Spring Boot集成更好,功能更强大。
  4. 实现分层解耦 :在DAO和Servlet之间引入 Service(业务逻辑层) 。Servlet只负责接收请求和响应,具体的业务逻辑(如销售时的库存检查、事务管理)放在Service层,使代码结构更清晰,职责更明确。
  5. 增强安全性
    • 密码加密 :不要使用MD5,应使用 BCryptPasswordEncoder 等加盐哈希算法。
    • SQL注入防护 :坚持使用 PreparedStatement (我们已用),切勿拼接SQL字符串。
    • XSS防护 :对用户输入进行过滤或转义,或在JSP中使用 <c:out value="${value}" /> 输出。
    • 会话管理 :对需要登录的页面,在Servlet或过滤器中检查Session中是否存在用户信息。
  6. 加入日志 :使用 SLF4J + Logback 记录系统运行日志、错误日志和操作日志,便于线上问题排查。
  7. 前端优化 :使用 Bootstrap 等CSS框架快速构建美观的响应式界面。使用 jQuery Vue.js 实现更流畅的交互,如异步加载数据、表单验证等。

这个超市管理系统项目麻雀虽小,五脏俱全。从环境搭建、数据库设计、到后端逻辑编写、前端页面展示,你完整地走通了一个Java Web应用的生命周期。理解了这个流程,你再学习Spring Boot等高级框架时,就会明白它们本质上是在简化这个流程中的各个步骤。建议你不仅完成代码复制,更要尝试自己添加“销售统计”、“用户权限控制”等功能,遇到问题就去搜索、调试,这才是能力提升的关键。项目的完整源码可以在文末的链接中获取,希望能为你后续的学习和面试带来实实在在的帮助。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐