Java Web超市管理系统实战:从零搭建MVC架构与事务处理
如果你是一名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. 项目核心功能与系统设计
我们的超市管理系统将包含以下核心功能模块,这也是一个管理系统最典型的组成部分:
- 用户管理: 系统登录、注销、权限控制(如管理员与普通收银员)。
- 商品管理: 商品的增、删、改、查,以及分类管理。
- 供应商管理: 维护商品供应商信息。
- 库存管理: 记录商品入库(采购)、出库(销售)流水,动态计算库存。
- 销售管理: 模拟收银台功能,生成销售单据。
- 统计报表: 按日、月统计销售额、毛利,分析畅销商品。
系统架构设计(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. 开发环境与工具准备
工欲善其事,必先利其器。请确保你的电脑上已安装以下环境:
- JDK: 版本 8 或 11(推荐8,兼容性最好)。安装后配置
JAVA_HOME环境变量。 - IDE: IntelliJ IDEA(终极版或社区版)或 Eclipse。本文演示以IDEA为主。
- Web服务器: Apache Tomcat 9.x。下载后解压即可。
- 数据库: MySQL 5.7 或 8.0。推荐使用图形化工具如Navicat或MySQL Workbench辅助操作。
- 项目管理: 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. 项目运行、部署与测试
- 数据库初始化: 运行前面提供的SQL脚本,创建数据库和表。可以插入一些测试数据。
- 配置数据库连接: 修改
db.properties中的用户名和密码。 - IDEA中配置Tomcat并启动: 将项目添加到Tomcat服务器,点击运行。
- 访问应用: 浏览器打开
http://localhost:8080/supermarket/login.jsp(假设你的应用上下文路径是/supermarket)。 - 功能测试:
- 登录测试: 使用预先插入的用户(如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. 项目优化与扩展建议(最佳实践)
完成基础版本后,你可以从以下方向深化项目,这会让你的项目在面试中脱颖而出:
- 引入前端框架: 将JSP视图替换为前后端分离架构。使用Vue.js或React构建前端,后端Servlet提供RESTful API(使用Jackson处理JSON)。这更符合现代开发趋势。
- 升级后端框架: 将原生Servlet替换为Spring Boot。你会体会到依赖注入、声明式事务管理、自动化配置带来的巨大便利。这是你从“会写代码”到“懂框架”的关键一步。
- 完善权限控制: 实现基于角色(RBAC)的权限管理。使用过滤器(Filter)或拦截器对请求进行拦截,检查用户会话和权限。
- 加入分页功能: 商品列表、销售记录等数据量大的查询必须支持分页。在SQL中使用
LIMIT,并在前端传递页码和大小参数。 - 数据验证: 在前端(JavaScript)和后端(Java Bean Validation)同时对用户输入进行验证,防止非法数据入库。
- 日志记录: 使用配置好的Logback,在关键业务节点(如登录、销售、库存变动)记录操作日志,便于问题追踪和审计。
- 代码优化:
- 使用连接池: 我们已经用了Druid,要理解其原理。
- DAO层模板化: 使用JdbcTemplate或MyBatis消除重复的JDBC代码。
- 服务层事务管理: 使用Spring的
@Transactional注解代替手动事务控制。
- 部署上线: 学习如何将项目打包成WAR文件,部署到云服务器(如阿里云ECS)的Tomcat上,并配置域名和Nginx反向代理。
这个超市管理系统项目,就像一副完整的骨架。你现在完成的是让它“站起来,能走路”。而上述的优化建议,则是为它注入血肉和灵魂,让它能“跑起来,干重活”。从环境搭建到数据库设计,从CRUD到事务处理,你走过的每一步,都是未来构建更复杂系统的基石。建议你将此项目作为你的个人作品,不断迭代和完善,在GitHub上维护一个清晰的代码仓库,这本身就是一份极具说服力的“能力证明”。
更多推荐



所有评论(0)