如果你是一名Java初学者,或者正在为期末项目、课程设计、毕业设计寻找一个“麻雀虽小,五脏俱全”的实战项目,那么这篇文章就是为你准备的。超市管理系统,这个听起来有些“经典”甚至“老套”的题目,恰恰是检验你Java Web技术栈掌握程度的绝佳试金石。很多人以为它只是简单的增删改查(CRUD),但一个真正能跑通、能演示、能写在简历里的项目,背后隐藏着从环境搭建、数据库设计、前后端交互到部署上线的完整工程化思维。

本文将带你从零开始,手把手实现一个功能完整的超市管理系统。我们不止于“半小时搞定”的噱头,而是要深入剖析每个环节:为什么选择这些技术?数据库表如何设计才合理?前后端数据如何流转?遇到坑怎么快速排查?最终,你将获得一个包含商品管理、员工管理、供应商管理、销售统计等核心模块,且代码结构清晰、易于扩展的项目。文末会提供完整的源码获取方式。

1. 为什么超市管理系统是Java Web入门的黄金项目?

在开始敲代码之前,我们先要搞清楚这个项目的价值。它绝不是一个过时的练习题,而是凝聚了企业级应用开发核心思想的微型样板。

第一,技术栈全面且经典。 一个标准的超市管理系统,几乎覆盖了Java Web开发的所有基础环节:

  • 后端: Servlet/JSP(或Spring MVC)、JDBC(或MyBatis)、JavaBean。
  • 前端: JSP、HTML、CSS、JavaScript、AJAX。
  • 数据库: MySQL(或Oracle)。
  • 服务器: Tomcat。
  • 工程管理: Maven。

通过这个项目,你能将散落的知识点(如Servlet生命周期、MVC模式、SQL编写、事务处理)串联成一个有机整体。

第二,业务逻辑贴近现实,易于理解。 “进货-库存-销售”的流程非常直观,这让你能更专注于技术实现,而不是纠结于复杂的业务规则。你可以清晰地看到一条数据(如一件商品)是如何在系统中被创建、查询、修改、关联(如与销售记录关联),并最终被删除或归档的。

第三,具备足够的扩展性和深度。 基础版本完成后,你可以以此为起点,进行无限深化:

  • 加深技术深度: 将JDBC替换为MyBatis或JPA;将原生Servlet/JSP升级为Spring Boot;引入Redis缓存商品信息;使用ECharts实现更酷炫的数据报表。
  • 扩展业务功能: 增加会员积分系统、促销活动模块、多仓库库存管理、移动端扫码入库等。

对于面试官而言,一个你能从头到尾讲清楚设计思路、技术选型和难点攻克的项目,远比罗列一堆技术名词更有说服力。接下来,我们就进入实战环节。

2. 项目核心功能与系统设计

我们的超市管理系统将包含以下核心功能模块,这也是一个管理系统最典型的组成部分:

  1. 用户管理: 系统登录、注销、权限控制(如管理员与普通收银员)。
  2. 商品管理: 商品的增、删、改、查,以及分类管理。
  3. 供应商管理: 维护商品供应商信息。
  4. 库存管理: 记录商品入库(采购)、出库(销售)流水,动态计算库存。
  5. 销售管理: 模拟收银台功能,生成销售单据。
  6. 统计报表: 按日、月统计销售额、毛利,分析畅销商品。

系统架构设计(MVC模式): 这是本项目的灵魂。我们将采用经典的JSP + Servlet + JavaBean模式,这也是理解更高级框架(如Spring MVC)的基础。

  • Model(模型,JavaBean/DAO): 对应数据库中的实体(如 Product , User )和访问这些实体的类( ProductDao , UserDao )。负责业务数据和业务逻辑。
  • View(视图,JSP): 用户看到的界面,如商品列表页( product_list.jsp )、登录页( login.jsp )。负责展示数据。
  • Controller(控制器,Servlet): ProductServlet LoginServlet 。接收用户请求(来自View),调用Model处理,并将结果返回给View。

这种分离使得代码结构清晰,易于维护和测试。

3. 开发环境与工具准备

工欲善其事,必先利其器。请确保你的电脑上已安装以下环境:

  1. JDK: 版本 8 或 11(推荐8,兼容性最好)。安装后配置 JAVA_HOME 环境变量。
  2. IDE: IntelliJ IDEA(终极版或社区版)或 Eclipse。本文演示以IDEA为主。
  3. Web服务器: Apache Tomcat 9.x。下载后解压即可。
  4. 数据库: MySQL 5.7 或 8.0。推荐使用图形化工具如Navicat或MySQL Workbench辅助操作。
  5. 项目管理: Maven 3.6+。IDEA通常内置。

在IDEA中配置Tomcat:

  • 打开 Run/Debug Configurations
  • 点击 + ,选择 Tomcat Server -> Local
  • Application server 处,点击 Configure... ,选择你的Tomcat解压目录。
  • Deployment 标签页,点击 + ,选择 Artifact ,添加你的Web项目。

环境配置是第一个“拦路虎”,如果遇到 ClassNotFoundException 404 错误,十有八九是环境或部署问题。请务必先确保一个简单的 Hello World JSP页面能在Tomcat上正常运行。

4. 数据库设计与建表

数据库设计是项目的基石。一个糟糕的表结构会让后续编码举步维艰。我们遵循第三范式进行基础设计,核心表如下:

user (用户表):

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码(建议MD5加密存储)',
  `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
  `role` varchar(20) NOT NULL DEFAULT 'cashier' COMMENT '角色:admin(管理员), cashier(收银员)',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';

product (商品表):

CREATE TABLE `product` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
  `product_no` varchar(50) NOT NULL COMMENT '商品编号(唯一)',
  `name` varchar(100) NOT NULL COMMENT '商品名称',
  `category` varchar(50) DEFAULT NULL COMMENT '分类',
  `purchase_price` decimal(10,2) NOT NULL COMMENT '进货价',
  `sale_price` decimal(10,2) NOT NULL COMMENT '零售价',
  `stock` int(11) NOT NULL DEFAULT '0' COMMENT '当前库存',
  `supplier_id` int(11) DEFAULT NULL COMMENT '供应商ID',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_product_no` (`product_no`),
  KEY `idx_supplier` (`supplier_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品信息表';

supplier (供应商表):

CREATE TABLE `supplier` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL COMMENT '供应商名称',
  `contact` varchar(50) DEFAULT NULL COMMENT '联系人',
  `phone` varchar(20) DEFAULT NULL COMMENT '电话',
  `address` varchar(200) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商表';

stock_log (库存流水表): 这是实现库存准确性的关键。

CREATE TABLE `stock_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) NOT NULL COMMENT '商品ID',
  `type` varchar(20) NOT NULL COMMENT '类型:purchase(采购入库), sale(销售出库), adjust(库存调整)',
  `quantity` int(11) NOT NULL COMMENT '变动数量(正数为入库,负数为出库)',
  `operator_id` int(11) DEFAULT NULL COMMENT '操作员ID',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_product` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表';

sale_order sale_item (销售订单与订单明细表): 这是一对多的关系,是处理销售业务的标准设计。

-- 销售订单主表
CREATE TABLE `sale_order` (
  `order_no` varchar(50) NOT NULL COMMENT '订单号(可生成唯一编号)',
  `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
  `cashier_id` int(11) NOT NULL COMMENT '收银员ID',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
  PRIMARY KEY (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单表';

-- 销售订单明细表
CREATE TABLE `sale_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_no` varchar(50) NOT NULL COMMENT '关联订单号',
  `product_id` int(11) NOT NULL COMMENT '商品ID',
  `quantity` int(11) NOT NULL COMMENT '销售数量',
  `sale_price` decimal(10,2) NOT NULL COMMENT '成交单价',
  `subtotal` decimal(10,2) NOT NULL COMMENT '小计金额',
  PRIMARY KEY (`id`),
  KEY `idx_order` (`order_no`),
  KEY `idx_product` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单明细表';

设计要点:

  • 唯一约束: user.username , product.product_no ,防止数据重复。
  • 索引: 在经常查询的字段上建立索引,如 product.supplier_id , stock_log.product_id
  • 数据类型: 金额使用 DECIMAL ,避免浮点数精度问题。
  • 时间戳: 使用 CURRENT_TIMESTAMP 自动记录创建和更新时间。
  • 外键关系: 虽然上述SQL未显式声明外键(考虑到性能和灵活性),但在逻辑上必须维护 product.supplier_id supplier.id 等关系的正确性。

5. 项目骨架搭建与Maven配置

我们使用Maven来管理项目依赖和构建。在IDEA中创建新项目,选择 Maven ,并勾选 Create from archetype ,选择 maven-archetype-webapp

关键的 pom.xml 依赖配置:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.supermarket</groupId>
    <artifactId>supermarket-management</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- Servlet & JSP API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
            <scope>provided</scope>
        </dependency>
        <!-- JSTL 标签库,用于在JSP中简化逻辑 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!-- 数据库连接池:Druid,性能好,监控功能强大 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.20</version>
        </dependency>

        <!-- 日志框架:SLF4J + Logback -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.12</version>
        </dependency>

        <!-- JSON处理:Gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10.1</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>supermarket</finalName>
        <plugins>
            <!-- 编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

项目目录结构: 创建标准的Maven Web项目结构:

src/main/java
    └── com.supermarket
        ├── controller (Servlet,控制器)
        ├── service (业务逻辑层)
        ├── dao (数据访问层,接口和实现)
        ├── model (JavaBean,实体类)
        └── util (工具类,如数据库连接、字符串处理)
src/main/resources
    └── db.properties (数据库配置文件)
src/main/webapp
    ├── WEB-INF
    │   ├── web.xml (部署描述符)
    │   └── lib (依赖jar包,Maven管理则无需手动放)
    ├── css
    ├── js
    ├── images
    └── *.jsp (视图页面)

6. 核心代码实现:从数据库连接到业务逻辑

我们以 商品管理 模块为例,贯穿MVC三层,展示核心代码。

第一步:模型层(Model) - 实体类与数据库连接 src/main/java/com/supermarket/model/Product.java :

package com.supermarket.model;

import java.math.BigDecimal;
import java.util.Date;

public class Product {
    private Integer id;
    private String productNo;
    private String name;
    private String category;
    private BigDecimal purchasePrice; // 使用BigDecimal处理金额
    private BigDecimal salePrice;
    private Integer stock;
    private Integer supplierId;
    private Date createTime;
    private Date updateTime;
    // 省略getter和setter方法,以及toString方法
}

src/main/java/com/supermarket/util/DBUtil.java (数据库连接工具类):

package com.supermarket.util;

import com.alibaba.druid.pool.DruidDataSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class DBUtil {
    private static DataSource dataSource;

    static {
        try {
            Properties props = new Properties();
            InputStream is = DBUtil.class.getClassLoader().getResourceAsStream("db.properties");
            props.load(is);

            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setDriverClassName(props.getProperty("jdbc.driver"));
            druidDataSource.setUrl(props.getProperty("jdbc.url"));
            druidDataSource.setUsername(props.getProperty("jdbc.username"));
            druidDataSource.setPassword(props.getProperty("jdbc.password"));
            // 其他连接池配置...
            dataSource = druidDataSource;
        } catch (Exception e) {
            throw new RuntimeException("初始化数据库连接池失败", e);
        }
    }

    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    public static void close(Connection conn) {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

src/main/resources/db.properties :

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/supermarket_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=your_password

第二步:数据访问层(DAO) src/main/java/com/supermarket/dao/ProductDao.java (接口):

package com.supermarket.dao;

import com.supermarket.model.Product;
import java.util.List;

public interface ProductDao {
    int addProduct(Product product);
    int deleteProductById(int id);
    int updateProduct(Product product);
    Product getProductById(int id);
    List<Product> getAllProducts();
    List<Product> getProductsByName(String name);
}

src/main/java/com/supermarket/dao/impl/ProductDaoImpl.java (实现类):

package com.supermarket.dao.impl;

import com.supermarket.dao.ProductDao;
import com.supermarket.model.Product;
import com.supermarket.util.DBUtil;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class ProductDaoImpl implements ProductDao {
    @Override
    public int addProduct(Product product) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        String sql = "INSERT INTO product(product_no, name, category, purchase_price, sale_price, stock, supplier_id) VALUES(?,?,?,?,?,?,?)";
        try {
            conn = DBUtil.getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, product.getProductNo());
            pstmt.setString(2, product.getName());
            pstmt.setString(3, product.getCategory());
            pstmt.setBigDecimal(4, product.getPurchasePrice());
            pstmt.setBigDecimal(5, product.getSalePrice());
            pstmt.setInt(6, product.getStock());
            pstmt.setInt(7, product.getSupplierId());
            return pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            return 0;
        } finally {
            // 关闭资源,实际项目应使用try-with-resources或工具方法
            DBUtil.close(conn);
        }
    }
    // 省略其他方法(delete, update, query)的实现...
}

第三步:业务逻辑层(Service) src/main/java/com/supermarket/service/ProductService.java :

package com.supermarket.service;

import com.supermarket.model.Product;
import java.util.List;

public interface ProductService {
    boolean addProduct(Product product);
    boolean deleteProduct(int id);
    boolean updateProduct(Product product);
    Product getProductById(int id);
    List<Product> getAllProducts();
    List<Product> searchProducts(String keyword);
}

src/main/java/com/supermarket/service/impl/ProductServiceImpl.java :

package com.supermarket.service.impl;

import com.supermarket.dao.ProductDao;
import com.supermarket.dao.impl.ProductDaoImpl;
import com.supermarket.model.Product;
import com.supermarket.service.ProductService;
import java.util.List;

public class ProductServiceImpl implements ProductService {
    private ProductDao productDao = new ProductDaoImpl();

    @Override
    public boolean addProduct(Product product) {
        // 这里可以添加业务逻辑,例如检查商品编号是否重复
        if (productDao.getProductByNo(product.getProductNo()) != null) {
            return false; // 编号重复
        }
        return productDao.addProduct(product) > 0;
    }
    // 省略其他方法实现...
}

第四步:控制层(Controller - Servlet) src/main/java/com/supermarket/controller/ProductServlet.java :

package com.supermarket.controller;

import com.supermarket.model.Product;
import com.supermarket.service.ProductService;
import com.supermarket.service.impl.ProductServiceImpl;
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("/product/*") // 使用路径映射
public class ProductServlet extends HttpServlet {
    private ProductService productService = new ProductServiceImpl();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String pathInfo = req.getPathInfo(); // 获取 /add, /delete, /list 等路径
        if ("/list".equals(pathInfo)) {
            List<Product> productList = productService.getAllProducts();
            req.setAttribute("productList", productList);
            req.getRequestDispatcher("/WEB-INF/jsp/product_list.jsp").forward(req, resp);
        } else if ("/edit".equals(pathInfo)) {
            String id = req.getParameter("id");
            Product product = productService.getProductById(Integer.parseInt(id));
            req.setAttribute("product", product);
            req.getRequestDispatcher("/WEB-INF/jsp/product_edit.jsp").forward(req, resp);
        }
        // 处理其他GET请求...
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8"); // 处理中文乱码
        String pathInfo = req.getPathInfo();
        if ("/add".equals(pathInfo)) {
            Product product = new Product();
            product.setProductNo(req.getParameter("productNo"));
            product.setName(req.getParameter("name"));
            // ... 设置其他属性
            boolean success = productService.addProduct(product);
            if (success) {
                resp.sendRedirect(req.getContextPath() + "/product/list"); // 重定向到列表页
            } else {
                req.setAttribute("errorMsg", "添加商品失败,可能编号重复");
                req.getRequestDispatcher("/WEB-INF/jsp/product_add.jsp").forward(req, resp);
            }
        }
        // 处理其他POST请求(更新、删除)...
    }
}

第五步:视图层(View - JSP) src/main/webapp/WEB-INF/jsp/product_list.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>
    <h1>商品列表</h1>
    <a href="${pageContext.request.contextPath}/product/addPage">新增商品</a>
    <table border="1" cellspacing="0">
        <tr>
            <th>编号</th><th>名称</th><th>分类</th><th>进货价</th><th>零售价</th><th>库存</th><th>操作</th>
        </tr>
        <c:forEach items="${productList}" var="product">
            <tr>
                <td>${product.productNo}</td>
                <td>${product.name}</td>
                <td>${product.category}</td>
                <td>${product.purchasePrice}</td>
                <td>${product.salePrice}</td>
                <td>${product.stock}</td>
                <td>
                    <a href="${pageContext.request.contextPath}/product/edit?id=${product.id}">编辑</a>
                    <a href="javascript:if(confirm('确定删除吗?')) location.href='${pageContext.request.contextPath}/product/delete?id=${product.id}'">删除</a>
                </td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

7. 核心业务:销售与库存联动的实现

这是项目的难点和亮点。销售商品时,必须保证 库存减少 生成销售记录 这两个操作在一个数据库事务中完成,否则会导致数据不一致(比如卖了商品但库存没减)。

服务层实现销售逻辑 ( SaleService ):

package com.supermarket.service.impl;

import com.supermarket.dao.ProductDao;
import com.supermarket.dao.SaleDao;
import com.supermarket.dao.StockLogDao;
import com.supermarket.model.SaleOrder;
import com.supermarket.model.SaleItem;
import com.supermarket.model.StockLog;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class SaleServiceImpl {
    private ProductDao productDao;
    private SaleDao saleDao;
    private StockLogDao stockLogDao;
    // 假设有一个获取数据库连接并管理事务的工具类
    private Connection conn = DBUtil.getConnection(); // 注意:实际应用应从线程上下文获取连接

    public boolean makeSale(SaleOrder order, List<SaleItem> items) {
        // 关键:开启事务,手动控制
        try {
            conn.setAutoCommit(false); // 关闭自动提交

            // 1. 插入销售订单主表
            saleDao.insertOrder(order);
            
            // 2. 循环插入销售明细,并更新对应商品库存
            for (SaleItem item : items) {
                saleDao.insertItem(item);
                // 更新商品库存(减少)
                int affectedRows = productDao.updateStock(item.getProductId(), -item.getQuantity());
                if (affectedRows == 0) {
                    throw new RuntimeException("商品库存不足或不存在,商品ID: " + item.getProductId());
                }
                // 3. 记录库存流水(出库)
                StockLog log = new StockLog();
                log.setProductId(item.getProductId());
                log.setType("sale");
                log.setQuantity(-item.getQuantity()); // 出库为负数
                log.setRemark("销售出库,订单号:" + order.getOrderNo());
                stockLogDao.insert(log);
            }
            // 所有操作成功,提交事务
            conn.commit();
            return true;
        } catch (Exception e) {
            // 任何一步出错,回滚事务
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
            return false;
        } finally {
            try {
                conn.setAutoCommit(true); // 恢复自动提交模式
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

关键点: setAutoCommit(false) commit() rollback() 。这确保了“扣库存”和“记销售”要么同时成功,要么同时失败,数据永远一致。

8. 项目运行、部署与测试

  1. 数据库初始化: 运行前面提供的SQL脚本,创建数据库和表。可以插入一些测试数据。
  2. 配置数据库连接: 修改 db.properties 中的用户名和密码。
  3. IDEA中配置Tomcat并启动: 将项目添加到Tomcat服务器,点击运行。
  4. 访问应用: 浏览器打开 http://localhost:8080/supermarket/login.jsp (假设你的应用上下文路径是 /supermarket )。
  5. 功能测试:
    • 登录测试: 使用预先插入的用户(如admin/123456)登录。
    • 商品管理测试: 尝试添加、查询、修改、删除商品。
    • 销售测试: 在销售界面选择商品、输入数量,完成销售。检查库存是否准确减少,销售订单和流水是否生成。
    • 报表测试: 查看销售统计报表。

9. 常见问题与排查指南(Q&A)

在开发过程中,你几乎一定会遇到以下问题:

问题现象 可能原因 排查方式 解决方案
访问JSP页面报404错误 1. 项目未成功部署到Tomcat。
2. URL路径错误。
3. web.xml 配置错误或Servlet注解未生效。
1. 检查IDEA中Tomcat的 Deployment 选项卡,确保项目Artifact已添加。
2. 检查浏览器地址栏URL是否与应用上下文路径匹配。
3. 检查 @WebServlet 注解路径或 web.xml 中的 <url-pattern>
1. 重新部署项目。
2. 使用 ${pageContext.request.contextPath} 获取正确路径。
3. 清理Tomcat工作目录并重启。
页面中文乱码 1. JSP页面编码未设置。
2. Servlet未设置请求/响应编码。
3. 数据库连接字符集不对。
1. 检查JSP文件头 <%@ page pageEncoding="UTF-8"%>
2. 在Servlet的 doPost 方法开始处调用 request.setCharacterEncoding("UTF-8") response.setContentType("text/html;charset=UTF-8")
3. 检查MySQL连接URL是否包含 characterEncoding=UTF-8
统一所有环节的编码为UTF-8。
数据库连接失败 1. MySQL服务未启动。
2. db.properties 配置错误(密码、端口、数据库名)。
3. 驱动版本与MySQL版本不匹配。
1. 检查MySQL服务状态。
2. 使用Navicat等工具测试连接信息。
3. 查看Tomcat日志中的具体SQLException信息。
1. 启动MySQL服务。
2. 核对配置文件。
3. 更换合适的JDBC驱动版本(如MySQL 8.0使用 com.mysql.cj.jdbc.Driver )。
插入数据失败,但无报错 1. 事务未提交。
2. 程序捕获了异常但未打印或处理。
1. 检查DAO层操作后是否调用了 connection.commit() (如果关闭了自动提交)。
2. 在catch块中打印 e.printStackTrace()
1. 确保事务正确提交。
2. 完善异常处理,将日志输出到控制台或文件。
页面显示“No suitable driver found” JDBC驱动未加载。 检查项目的 WEB-INF/lib 目录下是否有 mysql-connector-java-xxx.jar ,或Maven依赖是否下载成功。 确保依赖正确。Maven项目可以执行 mvn clean compile 检查。

10. 项目优化与扩展建议(最佳实践)

完成基础版本后,你可以从以下方向深化项目,这会让你的项目在面试中脱颖而出:

  1. 引入前端框架: 将JSP视图替换为前后端分离架构。使用Vue.js或React构建前端,后端Servlet提供RESTful API(使用Jackson处理JSON)。这更符合现代开发趋势。
  2. 升级后端框架: 将原生Servlet替换为Spring Boot。你会体会到依赖注入、声明式事务管理、自动化配置带来的巨大便利。这是你从“会写代码”到“懂框架”的关键一步。
  3. 完善权限控制: 实现基于角色(RBAC)的权限管理。使用过滤器(Filter)或拦截器对请求进行拦截,检查用户会话和权限。
  4. 加入分页功能: 商品列表、销售记录等数据量大的查询必须支持分页。在SQL中使用 LIMIT ,并在前端传递页码和大小参数。
  5. 数据验证: 在前端(JavaScript)和后端(Java Bean Validation)同时对用户输入进行验证,防止非法数据入库。
  6. 日志记录: 使用配置好的Logback,在关键业务节点(如登录、销售、库存变动)记录操作日志,便于问题追踪和审计。
  7. 代码优化:
    • 使用连接池: 我们已经用了Druid,要理解其原理。
    • DAO层模板化: 使用JdbcTemplate或MyBatis消除重复的JDBC代码。
    • 服务层事务管理: 使用Spring的 @Transactional 注解代替手动事务控制。
  8. 部署上线: 学习如何将项目打包成WAR文件,部署到云服务器(如阿里云ECS)的Tomcat上,并配置域名和Nginx反向代理。

这个超市管理系统项目,就像一副完整的骨架。你现在完成的是让它“站起来,能走路”。而上述的优化建议,则是为它注入血肉和灵魂,让它能“跑起来,干重活”。从环境搭建到数据库设计,从CRUD到事务处理,你走过的每一步,都是未来构建更复杂系统的基石。建议你将此项目作为你的个人作品,不断迭代和完善,在GitHub上维护一个清晰的代码仓库,这本身就是一份极具说服力的“能力证明”。

Logo

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

更多推荐