首页 / 资讯中心 / 文章详情

基于JSP的网上玩具销售系统:从环境搭建到下单事务的完整实现

基于JSP的网上玩具销售系统:从环境搭建到下单事务的完整实现 ★ FEATURED ARTICLE
简介这是一份面向高校计算机相关专业毕业设计的完整项目源码包主题为基于JSP的网上玩具销售系统适合正在准备毕设或需要Java Web实战案例的学生与开发者参考。项目以JavaServer Pages为核心结合Servlet、JavaBeans实现服务端逻辑涵盖商品浏览、分类导航、购物车、用户中心与订单管理等典型电商模块并涉及数据库设计与会话安全等知识点。压缩包共1059个文件约23.2MB包含76个jsp页面、98个html、29个css及大量js与图片素材另有7个java源文件、7个class、5个jar与1个sql脚本以及doc项目文档便于理解整体结构与部署流程。目前已有390人学习下载。通过该资源可获取一套可运行的毕设参考方案帮助梳理JSP开发流程、数据库建表思路与前后端交互方式适合作为课程设计或自学练手素材。1. 基于 JSP 的网上玩具销售系统从选题到能跑通的完整落地路径很多同学拿到「基于 JSP 的网上玩具销售系统」这个毕业设计题目时第一反应是去 GitHub 找一个现成项目改改交差。但真正动手才发现JSP 项目不像 Spring Boot 那样开箱即用Tomcat 版本、JDK 版本、数据库驱动、JSTL 标签库任何一个环节对不上就是 404 或 500。我带过几届学生的毕设最常见的翻车场景不是代码写不出来而是环境跑不起来、页面能打开但数据出不来、答辩时被问「你这个登录是怎么验证的」答不上来。这篇笔记面向的是选了 JSP 方向、需要从零搭出一个能演示、能答辩、能讲清楚原理的网上玩具销售系统的同学。我会按「技术选型 → 数据库设计 → 核心功能实现 → 避坑 → 进阶」的顺序把每个环节的关键代码和参数配置写清楚。读完你应该能独立跑通一个包含商品展示、购物车、下单、后台管理的完整系统并且知道每一层为什么这么写。2. 技术选型与环境搭建为什么 JSP 项目最容易死在第一步2.1 JSP Servlet JDBC 三层结构到底怎么分JSP 本身是服务端页面技术最终会被容器编译成 Servlet。很多教程把业务逻辑直接写在 JSP 的% %脚本片段里这样写小 demo 没问题但毕设要展示架构能力答辩老师一定会问「你的 MVC 体现在哪里」。所以推荐的结构是JSP 层只负责展示和表单提交不写 SQL不写业务判断Servlet 层接收请求、调用 Service、控制跳转Service 层业务逻辑比如「下单时扣库存 生成订单 清空购物车」DAO 层只做数据库增删改查一个方法对应一条 SQL实体类Model和数据库表一一对应的 JavaBean这个分层不是为了好看而是为了答辩时有话讲。比如老师问「如果我要把 MySQL 换成 Oracle 怎么办」你可以说「只需要改 DAO 层的实现Service 和 Servlet 不动」。2.2 环境版本组合一套经过验证的配置JSP 项目对版本极其敏感下面这套组合是我在多个机器上验证过能跑通的组件版本说明JDK8 或 11JDK 17 以上和旧版 Tomcat 有兼容问题Tomcat8.5 或 9.0不要用 Tomcat 10包名从 javax 变成 jakartaMySQL5.7 或 8.08.0 需要改驱动类和连接串mysql-connector-java5.1.49 或 8.0.28与 MySQL 版本对应JSTL1.2必须放对位置否则标签不解析IDEIDEA 或 EclipseIDEA 需要配置 Artifact提示Tomcat 10 把javax.servlet全部改成了jakarta.servlet网上绝大多数 JSP 教程代码在 Tomcat 10 下直接编译报错。毕设求稳用 Tomcat 9。2.3 在 IDEA 里跑通第一个 JSP 页面的最小步骤新建项目后目录结构应该是这样toy-shop/ ├── src/ │ └── main/ │ ├── java/ │ │ └── com/toy/ │ │ ├── dao/ │ │ ├── service/ │ │ ├── servlet/ │ │ └── entity/ │ └── webapp/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ │ ├── css/ │ ├── js/ │ └── index.jspweb.xml最小配置?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list /web-appindex.jsp先写一个能显示当前时间的页面验证环境% page contentTypetext/html;charsetUTF-8 languagejava % % page importjava.util.Date % html headtitle玩具商城/title/head body h1网上玩具销售系统/h1 p服务器时间% new Date() %/p /body /html配置 Tomcat Run Configuration 时注意 Deployment 标签页里 Application context 设为/toy这样访问地址就是http://localhost:8080/toy/index.jsp。如果这一步 404先检查 Artifact 有没有正确添加 Web Application: Exploded。3. 数据库设计与实体映射玩具销售系统的表怎么建才不返工3.1 六张核心表与字段设计玩具销售系统的数据模型比通用商城简单但有几个玩具品类特有的点适龄段、安全认证、品牌。下面是我一般会用的表结构CREATE DATABASE toy_shop DEFAULT CHARSET utf8mb4; USE toy_shop; -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, phone VARCHAR(20), address VARCHAR(200), role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 商品表 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, age_range VARCHAR(20) COMMENT 适龄段如3-6岁, brand VARCHAR(50), image VARCHAR(200), description TEXT, status TINYINT DEFAULT 1 COMMENT 1上架 0下架 ); -- 分类表 CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ); -- 购物车表 CREATE TABLE cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, product_id INT NOT NULL, quantity INT DEFAULT 1, add_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单表 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE NOT NULL, user_id INT NOT NULL, total_amount DECIMAL(10,2), status TINYINT DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成, 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, quantity INT, price DECIMAL(10,2) );注意订单表我用了orders而不是order因为order是 SQL 关键字用它会报语法错误这是血泪经验。3.2 实体类与 DAO 的对应写法以 Product 为例实体类字段名和表字段一一对应package com.toy.entity; public class Product { private Integer id; private String name; private Double price; private Integer stock; private Integer categoryId; private String ageRange; private String brand; private String image; private String description; private Integer status; // getter/setter 省略 }DAO 层用 JDBC 工具类统一管理连接避免每个方法都写一遍 DriverManagerpackage com.toy.util; import java.sql.*; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/toy_shop?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) {} try { if (ps ! null) ps.close(); } catch (SQLException e) {} try { if (conn ! null) conn.close(); } catch (SQLException e) {} } }连接串里的serverTimezoneAsia/Shanghai必须加否则 MySQL 8.0 会报时区错误。characterEncodingutf8不加的话中文商品名会变成问号。3.3 商品查询的 DAO 实现与分页参数商品列表页需要分页核心 SQL 用LIMITpublic ListProduct findByPage(int page, int pageSize, String keyword) { ListProduct list new ArrayList(); String sql SELECT * FROM product WHERE status1 AND name LIKE ? LIMIT ?,?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % (keyword null ? : keyword) %); ps.setInt(2, (page - 1) * pageSize); ps.setInt(3, pageSize); ResultSet rs ps.executeQuery(); while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getDouble(price)); p.setImage(rs.getString(image)); p.setAgeRange(rs.getString(age_range)); list.add(p); } } catch (SQLException e) { e.printStackTrace(); } return list; }page从 1 开始(page-1)*pageSize是偏移量。如果前端传的 page 是 0 或者负数这里会抛异常所以 Servlet 层要做一次校验int page req.getParameter(page) null ? 1 : Integer.parseInt(...)并且判断小于 1 就设为 1。4. 核心功能实现从商品展示到下单的完整链路4.1 商品列表页的 JSTL 渲染与图片路径处理JSP 里不要用脚本片段遍历列表用 JSTL 的c:forEach% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % ... div classproduct-list c:forEach items${productList} varp div classproduct-item img src${pageContext.request.contextPath}/upload/${p.image} alt${p.name} width200 height200 h3${p.name}/h3 p适龄${p.ageRange}/p p classpricefmt:formatNumber value${p.price} pattern0.00//p a href${pageContext.request.contextPath}/product/detail?id${p.id}查看详情/a /div /c:forEach /div${pageContext.request.contextPath}会输出/toy这样图片路径就是/toy/upload/xxx.jpg。如果直接写/upload/xxx.jpg在非根 context 下会 404。图片上传目录建议放在 webapp 下的upload文件夹Tomcat 部署后可以直接访问。4.2 购物车Session 还是数据库购物车有两种实现方式毕设里我推荐用数据库存因为答辩时老师更认可「数据持久化」的说法。但 Session 实现更简单适合时间紧的情况。这里给数据库版本的 ServletWebServlet(/cart/add) public class CartAddServlet extends HttpServlet { private CartService cartService new CartServiceImpl(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { User user (User) req.getSession().getAttribute(user); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } int productId Integer.parseInt(req.getParameter(productId)); int quantity Integer.parseInt(req.getParameter(quantity)); cartService.addToCart(user.getId(), productId, quantity); resp.sendRedirect(req.getContextPath() /cart/list); } }Service 层的addToCart要先查购物车里是否已有该商品有就更新数量没有就插入public void addToCart(int userId, int productId, int quantity) { Cart existing cartDao.findByUserAndProduct(userId, productId); if (existing ! null) { cartDao.updateQuantity(existing.getId(), existing.getQuantity() quantity); } else { Cart cart new Cart(); cart.setUserId(userId); cart.setProductId(productId); cart.setQuantity(quantity); cartDao.insert(cart); } }4.3 下单流程事务处理与库存扣减下单是最容易出 bug 的地方。必须保证「扣库存、生成订单、生成订单明细、清空购物车」四步在同一个事务里。JDBC 手动事务写法public String createOrder(int userId, ListCart cartList) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 计算总金额并校验库存 double total 0; for (Cart cart : cartList) { Product p productDao.findById(conn, cart.getProductId()); if (p.getStock() cart.getQuantity()) { conn.rollback(); return 库存不足 p.getName(); } total p.getPrice() * cart.getQuantity(); } // 2. 生成订单 String orderNo System.currentTimeMillis() userId; int orderId orderDao.insert(conn, orderNo, userId, total); // 3. 扣库存 写明细 for (Cart cart : cartList) { productDao.reduceStock(conn, cart.getProductId(), cart.getQuantity()); orderItemDao.insert(conn, orderId, cart.getProductId(), cart.getQuantity(), cart.getPrice()); } // 4. 清空购物车 cartDao.deleteByUser(conn, userId); conn.commit(); return orderNo; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) {} e.printStackTrace(); return null; } finally { try { if (conn ! null) conn.setAutoCommit(true); } catch (SQLException e) {} DBUtil.close(conn, null, null); } }关键点所有 DAO 方法都要接收同一个Connection参数不能各自去DBUtil.getConnection()否则事务不生效。这是 JSP 毕设里最隐蔽的坑之一。4.4 后台管理的权限拦截后台页面不能只靠「不显示链接」来保护必须在 Servlet 里判断角色WebServlet(/admin/*) public class AdminFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; User user (User) request.getSession().getAttribute(user); if (user null || user.getRole() ! 1) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }用 Filter 拦截/admin/*路径比在每个 Servlet 里写判断更可靠。web.xml 里配置 filter-mapping 或者用WebFilter注解都可以。5. 避坑与排查JSP 玩具商城最常见的 5 个翻车现场5.1 现象页面显示 404控制台无报错原因URL 路径写错或者 Servlet 的WebServlet注解值和访问路径不一致。常见的是 JSP 里表单 action 写了/cart/add但项目 context 是/toy实际应该访问/toy/cart/add。解决所有链接和表单 action 统一用${pageContext.request.contextPath}/xxx不要手写绝对路径。在浏览器 F12 的 Network 里看实际请求 URL 和响应状态。5.2 现象JSTL 标签不解析页面直接显示c:forEach原因jstl.jar和standard.jar没有放到WEB-INF/lib下或者只放到了 IDEA 的 Libraries 里但没有打包进 Artifact。解决确认WEB-INF/lib下有jstl-1.2.jar然后在 IDEA 的 Project Structure → Artifacts → 输出布局里确认这个 jar 被包含。Tomcat 9 只需要jstl-1.2.jar一个文件。5.3 现象中文商品名存入数据库变成???原因数据库连接串没有加characterEncodingutf8或者数据库建库时没有指定utf8mb4。解决连接串加characterEncodingutf8建库用DEFAULT CHARSET utf8mb4JSP 页面头部加% page contentTypetext/html;charsetUTF-8 %。三处都对了才不会乱码。5.4 现象下单后库存没变但订单生成了原因DAO 方法各自获取了独立的 Connection导致事务没有覆盖所有操作。解决把 Connection 作为参数在 DAO 方法间传递Service 层统一控制setAutoCommit(false)和commit/rollback。检查每个 DAO 方法是不是都用了传入的 conn。5.5 现象Tomcat 启动报ClassNotFoundException: com.mysql.cj.jdbc.Driver原因MySQL 驱动 jar 没有放到WEB-INF/lib或者 MySQL 5.x 用了com.mysql.cj.jdbc.Driver5.x 应该用com.mysql.jdbc.Driver。解决MySQL 8.0 用com.mysql.cj.jdbc.Drivermysql-connector-java-8.0.28.jarMySQL 5.7 用com.mysql.jdbc.Drivermysql-connector-java-5.1.49.jar。驱动 jar 必须放WEB-INF/lib不要只加在 IDEA 的模块依赖里。6. 进阶技巧让毕设从「能跑」变成「能拿高分」6.1 用过滤器统一解决 POST 中文乱码每个 Servlet 都写req.setCharacterEncoding(UTF-8)太啰嗦用一个 Filter 统一处理WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }这个 Filter 要放在所有其他 Filter 之前执行WebFilter的 order 不好控制的话就在 web.xml 里按顺序配置。6.2 商品图片上传的坐标定位裁剪思路热搜里有人问「jsp图片如何对坐标定位」实际场景是商品主图需要统一尺寸。常见做法是前端用 cropper.js 让用户框选区域拿到 x、y、width、height 四个参数后端用 Java 的BufferedImage裁剪public static void cropImage(String src, String dest, int x, int y, int width, int height) throws IOException { BufferedImage original ImageIO.read(new File(src)); BufferedImage cropped original.getSubimage(x, y, width, height); ImageIO.write(cropped, jpg, new File(dest)); }getSubimage的四个参数就是前端传过来的坐标和尺寸。注意 xwidth 不能超过原图宽度否则抛RasterFormatException所以后端要加边界校验。6.3 答辩时怎么讲清楚「JSP 和 Servlet 的关系」这是高频问题。你可以这样回答JSP 本质是一个特殊的 Servlet第一次被访问时会被 Tomcat 的 Jasper 编译器翻译成 Java 文件再编译成 class 文件。JSP 里写的 HTML 会被out.write()输出脚本片段会变成_jspService方法里的代码。所以 JSP 适合做展示Servlet 适合做控制两者配合就是 MVC。能讲清楚这个老师基本不会再追问。6.4 一个我踩过的坑不要在 JSP 里直接 new DAO早期我图省事在 JSP 里写% ProductDao dao new ProductDao(); %然后遍历数据。这样写页面能出来但一旦数据库连接出问题整个页面直接 500而且异常堆栈指向 JSP 编译后的文件根本看不懂。后来全部改成 Servlet 先查好数据req.setAttribute传到 JSPJSP 只负责渲染。这个习惯让我后面调 bug 的时间少了一半。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站