☰
JSP+MySQL+Tomcat网上购物系统实战:环境搭建、源码解析与避坑指南
2026/10/7 12:50:08 网站建设 项目流程

简介:这份资源是一套基于JSP、MySQL与Tomcat实现的网上购物系统Java源码,面向正在学习Java Web开发、希望以完整项目理解电商业务逻辑的初学者与进阶开发者。系统涵盖用户注册登录、商品分类展示与搜索、购物车增删改与总价计算、订单下单支付及状态管理、商品评论等核心模块,后端以Servlet与JavaBean处理业务,JSP负责页面渲染,MySQL存储商品、用户与订单数据,Tomcat承载应用运行。压缩包共787个文件,约11.02MB,包含75个java源文件、63个jsp页面、75个class编译文件、38个jar依赖包,以及gif、jpg等图片素材和xml、sql、properties等配置与建表脚本,目录结构完整,便于对照源码梳理分层设计。目前已有115人学习下载,适合作为课程设计或毕业设计的实战参考,帮助读者掌握数据库表结构设计、前后端交互流程与常见问题的排查思路。

1. 网上购物系统 jsp+mysql+tomcat 这套老架构,为什么还值得动手跑一遍

很多人第一次接触 Java Web,就是从一套网上购物系统源码开始的:jsp 写页面、mysql 存数据、tomcat 当容器,三个东西凑齐就能跑出一个能注册、能下单、能看订单的完整站点。这套组合看着老,但它把「请求怎么进来、业务怎么处理、数据怎么落库、页面怎么渲染」这条链路完整暴露在你面前,没有 Spring 帮你藏,也没有前端框架帮你挡。你改一行 jsp 就能看到页面变化,改一句 SQL 就能看到数据变化,这种即时反馈对建立工程直觉特别有用。

这篇文章面向两类人:一类是刚学完 java 基础、想找个能跑起来的项目练手的新手;另一类是工作几年、想回头把 Web 底层补一遍的开发者。我会按「环境怎么搭 → 数据库怎么建 → 源码怎么读 → 功能怎么改 → 坑怎么避」的顺序讲,每一步都给可复制的命令和配置。你跟着走完,手里会有一套能本地运行、能自己加功能的购物系统,而不是一个只能看不能动的压缩包。

2. 把 jsp+mysql+tomcat 跑起来:环境、数据库、部署三步走

2.1 先定版本组合,别让 jdk 和 tomcat 打架

这套老架构最怕的就是版本乱配。jsp 和 servlet 的规范是绑在 tomcat 版本上的,tomcat 又依赖特定区间的 jdk。我一般会锁定一套经过验证的组合:jdk 8、tomcat 8.5、mysql 5.7 或 8.0。原因很直接——网上购物系统这类源码大多写于 servlet 3.1 时代,用的还是javax.servlet包名,tomcat 9 之后虽然还兼容,但 tomcat 10 换成了jakarta.servlet,老源码直接编译不过。jdk 方面,jdk 8 对老项目的兼容性最好,jdk 11 以上偶尔会遇到反射和模块化相关的报错。

安装 jdk 后要确认JAVA_HOME指向 jdk 根目录而不是 bin 目录,这是新手最常翻车的地方。tomcat 下载解压后,先别急着放项目,双击bin/startup.bat(Linux 下startup.sh)看能不能起来,浏览器访问http://localhost:8080出现 tomcat 默认页,说明容器本身没问题。mysql 安装时记住 root 密码,字符集选utf8mb4,排序规则用utf8mb4_general_ci,不然后面中文商品名会变问号。

提示:如果你用 IDEA,配置 tomcat 时选「Local」,在 Deployment 里加一个 Artifact,Application context 设成/shop,这样访问路径就是http://localhost:8080/shop,和源码里的相对路径对得上。

2.2 建库建表:把购物系统的数据模型先立住

购物系统的数据模型不复杂,核心就几张表:用户表、商品表、分类表、订单表、订单明细表、购物车表。下面这段 SQL 是我从多套源码里归纳出来的最小可用结构,字段名做了统一,方便你对照 jsp 里的变量。

-- 创建数据库,字符集必须和连接串一致 CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE shop; -- 用户表:登录、注册、收货信息都靠它 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存 MD5 或明文,老源码多为明文 phone VARCHAR(20), address VARCHAR(200), reg_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 商品表:分类用 category_id 关联 CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, category_id INT, image VARCHAR(200), -- 存图片相对路径 intro TEXT ); -- 订单表:状态 0 待付款 1 已付款 2 已发货 3 已完成 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细:一个订单多条记录 CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, num INT NOT NULL, price DECIMAL(10,2) NOT NULL );

建表时几个参数要留意:price用DECIMAL而不是FLOAT,金额计算不能有精度误差;password长度给 64,方便以后换成 SHA-256;image只存相对路径,比如/upload/1.jpg,绝对路径换台机器就废了。执行完用SHOW TABLES;确认六张表都在,再往product里插两条测试数据,后面调试页面时才有东西可显示。

2.3 导入源码、改连接串、部署 war

拿到源码后先看目录结构,标准的老式 Java Web 项目长这样:src放 java 代码,WebContent或webapp放 jsp 和静态资源,WEB-INF/lib放 jar 包,WEB-INF/web.xml是部署描述符。第一步是找数据库连接配置,通常在一个叫DBUtil.java或db.properties的文件里。

// DBUtil.java 典型写法,重点看 url、user、password public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; public static Connection getConn() throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); // mysql 8 用 cj,5.7 用 com.mysql.jdbc.Driver return DriverManager.getConnection(URL, USER, PASSWORD); } }

这段代码有三个参数最容易出问题。useSSL=false不加会在启动时刷一堆警告,mysql 8 甚至直接连不上;serverTimezone不设会导致插入的时间差 8 小时;驱动类名 mysql 5.7 和 8.0 不一样,写错了报ClassNotFoundException。改完连接串,把项目打成 war 丢进 tomcat 的webapps目录,或者直接在 IDEA 里点运行,访问首页能看到商品列表,就算跑通了。

3. 读懂 jsp 源码:页面、Servlet、DAO 三层怎么串起来

3.1 jsp 页面里的 Java 代码块,到底在干什么

老式购物系统的 jsp 里经常能看到<% %>这种脚本片段,新手一看就懵。其实它就是把 Java 代码直接嵌进 HTML,tomcat 会把 jsp 编译成一个 Servlet,<% %>里的内容原样搬进_jspService方法。比如商品列表页常见的写法:

<%@ page import="java.util.List" %> <%@ page import="com.shop.entity.Product" %> <table> <% // 从 request 里取出 Servlet 传过来的商品列表 List<Product> list = (List<Product>) request.getAttribute("productList"); if (list != null) { for (Product p : list) { %> <tr> <td><%= p.getName() %></td> <td><%= p.getPrice() %></td> <td><a href="productDetail?id=<%= p.getId() %>">查看</a></td> </tr> <% } } %> </table>

<%= %>是输出表达式,等价于out.print()。这段逻辑说明 jsp 只负责展示,数据是 Servlet 通过request.setAttribute放进去的。理解这一点,你就能顺着「jsp 页面 → 找 form 的 action → 找对应 Servlet → 找 DAO 方法 → 找 SQL」这条线把整个功能读穿。热搜里常有人问「jsp 页面让加载完后刷新一次」,其实就是在这种页面里加一段setTimeout或 meta 刷新,但购物系统里慎用,会把用户填了一半的表单冲掉。

3.2 Servlet 做控制,DAO 做数据,职责别混

一套结构清晰的源码,Servlet 里只做三件事:取参数、调业务、跳页面。真正的 SQL 应该沉到 DAO 层。下面是一个登录 Servlet 的典型骨架:

@WebServlet("/login") public class LoginServlet extends HttpServlet { private UserDao userDao = new UserDao(); protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); // 参数校验不能省,空值直接打回 if (username == null || username.trim().isEmpty()) { req.setAttribute("msg", "用户名不能为空"); req.getRequestDispatcher("login.jsp").forward(req, resp); return; } User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { req.getSession().setAttribute("user", user); // 登录态存 session resp.sendRedirect("index"); // 重定向防表单重复提交 } else { req.setAttribute("msg", "用户名或密码错误"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } }

这里有两个关键选择:登录成功用sendRedirect而不是forward,避免用户刷新页面重复提交表单;登录态放session而不是request,因为 request 一次请求就没了。DAO 层就是拼 SQL、执行、封装结果,注意用PreparedStatement而不是字符串拼接,否则用户名填个' or '1'='1就能绕过登录,这是老源码里最常见的注入漏洞。

3.3 购物车和订单:session 与数据库怎么配合

购物车有两种实现:一种纯放 session,一种落数据库。老源码多用 session,实现简单但换设备就丢。核心逻辑是往 session 里放一个Map<Integer, Integer>,key 是商品 id,value 是数量。

// 加入购物车 Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } int pid = Integer.parseInt(req.getParameter("id")); cart.put(pid, cart.getOrDefault(pid, 0) + 1); resp.sendRedirect("cart.jsp");

下单时遍历这个 Map,把每条转成order_item插入,同时扣减product的库存。这里必须用事务:插订单、插明细、扣库存三步要么全成功要么全回滚,否则会出现订单有了库存没扣的脏数据。用Connection.setAutoCommit(false)手动控制,任何一步抛异常就rollback()。库存扣减建议写成UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?,把判断放进 SQL,避免并发超卖。

4. 改造与排错:让这套购物系统真正能用的几个动作

4.1 图片上传与坐标定位,别再写死路径

商品图片上传是老项目的痛点。常见做法是上传到项目外的固定目录,数据库只存文件名,jsp 里用统一的路径前缀拼接。热搜里有人问「jsp 图片如何对坐标定位」,本质是想做图片热区或水印,可以用 CSS 的position:absolute配合相对定位的父容器实现,但购物系统里更实用的是限制上传尺寸和格式。

// 上传核心:校验后缀 + 重命名防覆盖 String fileName = item.getName(); String suffix = fileName.substring(fileName.lastIndexOf(".")); if (!".jpg,.png,.gif".contains(suffix.toLowerCase())) { throw new RuntimeException("只允许图片格式"); } String newName = UUID.randomUUID().toString() + suffix; File dest = new File("D:/upload", newName); item.write(dest); // 依赖 commons-fileupload

参数上注意upload目录要在 tomcat 之外,重新部署 war 不会把用户上传的图冲掉;数据库存newName,页面用${pageContext.request.contextPath}/upload/${p.image}拼路径,换 context 也不会 404。

4.2 分页查询:limit 的两个参数别搞反

商品多了必须分页。mysql 用LIMIT offset, size,offset 是跳过的行数不是页码,这是新手最容易搞反的地方。第 1 页 offset 是 0,第 2 页是 size,第 n 页是(n-1)*size。

-- 第 3 页,每页 10 条,跳过前 20 条 SELECT * FROM product ORDER BY id DESC LIMIT 20, 10; -- 配套查总数,用于算总页数 SELECT COUNT(*) FROM product;

总页数用(total + size - 1) / size向上取整。jsp 里渲染上一页下一页时,页码要 clamp 在 1 到总页数之间,否则用户手改 URL 里的 page 参数会查出空列表甚至报错。

4.3 避坑清单:五个我踩过的真实问题

现象一:页面中文全是乱码。原因:jsp 文件编码、response 编码、数据库连接串编码三者不一致。解决:jsp 顶部加<%@ page contentType="text/html;charset=UTF-8" %>,Servlet 里resp.setContentType("text/html;charset=UTF-8"),连接串带characterEncoding=utf8,数据库和表都用utf8mb4,四处对齐。

现象二:tomcat 启动报 8080 端口被占用。原因:本机装了其他 web 服务或 tomcat 没关干净。解决:netstat -ano | findstr 8080找到进程号,任务管理器结束,或改conf/server.xml里的 Connector port。

现象三:mysql 8 连接报Public Key Retrieval is not allowed。原因:mysql 8 默认认证插件变了。解决:连接串加allowPublicKeyRetrieval=true,或者把用户认证方式改回mysql_native_password。

现象四:jsp 修改后刷新没变化。原因:tomcat 用了编译后的 class 缓存。解决:IDEA 里选 Update resources and classes,或直接重启 tomcat,生产环境别开热部署。

现象五:订单金额算出来差几分钱。原因:用 double 做金额运算。解决:数据库用 DECIMAL,Java 里用BigDecimal,setScale(2, RoundingMode.HALF_UP)统一舍入。

5. 从能跑到好用:给老购物系统加一层验证与安全习惯

跑通只是起点,真正让这套源码有价值的是你能在上面做二次开发。我一般会先加一个最简单的验证手段:写个HealthServlet,访问时返回数据库连接状态和商品总数,部署完先打这个接口,比翻页面快得多。

@WebServlet("/health") public class HealthServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("application/json;charset=UTF-8"); try (Connection conn = DBUtil.getConn()) { resp.getWriter().write("{\"db\":\"ok\"}"); } catch (Exception e) { resp.getWriter().write("{\"db\":\"fail\",\"msg\":\"" + e.getMessage() + "\"}"); } } }

进阶方向有三个,按投入产出排序。第一是安全:把所有 SQL 换成PreparedStatement,密码从明文改成加盐哈希,登录加验证码防爆破。第二是结构:把 jsp 里的 Java 代码逐步抽到 Servlet 和 DAO,jsp 只留展示,为以后换模板引擎铺路。第三是功能:加商品搜索、订单状态流转、后台管理,每加一个功能都走一遍「建表 → DAO → Servlet → jsp」的完整链路,练手效果最好。

下面这张表是我建议的改造优先级,你可以照着排期:

改造项难度收益建议顺序
SQL 参数化低堵住注入1
密码哈希低防拖库2
事务补全中数据一致3
分页与搜索中可用性4
后台管理高完整度5

最后说个习惯:每改一个功能,先在数据库里手动跑一遍 SQL 确认结果对,再去改 Java 代码,最后调页面。这个顺序能帮你把问题定位在某一层,而不是页面一报错就从头翻。这套 jsp+mysql+tomcat 的购物系统我前后搭过不下十遍,每次翻车都翻在版本和编码上,代码逻辑本身反而很少出问题。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询