尧图精选

基于JSP MVC与MySQL的图书购物网站课设:从数据库设计到war部署

🕒 发布时间:2026/10/1 5:27:17 📁 来源:尧图网络
简介这是一套面向高校计算机相关专业学生的JSP课程设计期末大作业资源基于JSPMVC模式与MySQL实现网上图书购物系统适合JavaWeb初学者、需要完成课程设计或期末大作业的同学参考与直接使用。压缩包共75个文件约47.77MB包含14个jsp页面、12个java源文件与12个class编译文件、3个css样式、3个xml配置、1个sql数据库脚本以及项目展示视频、说明文档和图片素材覆盖前端页面、后端控制、数据库与部署配置各环节。代码注释详尽新手也能读懂下载后简单部署即可运行。系统功能完善、界面美观、操作便捷具备图书浏览、购物车、订单管理等完整流程具有较高的实际应用价值。目前已有347人学习下载可作为课程设计、期末大作业的完整参考方案帮助读者快速理解MVC分层结构、JSP与MySQL交互方式及项目部署思路。1. 从一份 JSP 课设说起图书购物网站到底要交付什么每年期末总有一批人卡在同一个地方题目写着“基于 JSPMVC 模式和 MySQL 的网上图书购物系统”听起来像是把几个热搜词拼在一起真动手才发现要交付的东西比想象中具体得多。它不是一个“能跑就行”的 demo而是一套完整的 JavaWeb 项目有前台图书浏览、购物车、下单有后台图书管理和订单处理有数据库表结构和初始数据还要能打包成 war 丢进 Tomcat 跑起来。换句话说这是一次把 MVC 三层架构、JSP 页面、Servlet 控制器、JDBC 数据访问和 MySQL 数据库串成一条线的完整训练。这篇文章面向两类人一类是正在做 JSP 课程设计、需要一套能照着复现的完整案例另一类是想回头补 JavaWeb 基础、把“传统 JSP 项目怎么打包 war”“MVC 三层架构到底怎么分层”这些模糊概念落到代码上的从业者。我会按“先讲清结构再动手搭环境然后逐个模块实现最后说坑”的顺序走中间给到能直接抄的代码和参数。你不需要先看完一本 JavaWeb 教材跟着做就能把图书购物网站的主干跑通。2. 图书购物网站的 MVC 分层与数据库设计2.1 为什么这个课设必须用 MVC 三层架构很多人做 JSP 课设第一反应是“一个 JSP 页面里既查数据库又显示 HTML”写起来快但改起来是灾难。MVC 三层架构的价值在这个图书购物网站里体现得特别明显图书列表、图书详情、购物车、订单每个功能都涉及“取数据—处理逻辑—展示页面”三段如果混在一起改一个价格显示格式要翻十几个文件。常见做法是把项目分成三层。表现层用 JSP 负责渲染控制器层用 Servlet 接收请求、调用业务、决定跳转模型层用 JavaBean 承载数据、用 DAO 封装 JDBC 操作。这样图书的增删改查只改 DAO页面样式只改 JSP请求参数校验放在 Servlet。对课设来说这个分层不是为了“显得专业”而是让你在答辩时能指着包结构说清楚每一层在干什么。包结构我一般这样组织com.bookshop.entity放实体类com.bookshop.dao放数据库访问com.bookshop.service放业务逻辑com.bookshop.servlet放控制器com.bookshop.util放数据库连接工具。JSP 统一放在WEB-INF/views下避免被直接访问。这个约定后面所有代码都按它来。2.2 图书购物系统的数据库表怎么定数据库是整个系统的地基表结构定错后面 DAO 和页面全要返工。图书购物网站最少需要五张表用户表、图书表、分类表、购物车表、订单表订单主表加订单明细表更规范课设里可以合并成一张订单表加一个图书 ID 字段但推荐拆开。下面是我常用的建表语句字段类型和默认值都按 MySQL 8 的习惯来。注意价格用DECIMAL(10,2)而不是FLOAT金额计算用浮点会出精度问题这是血泪经验。CREATE DATABASE bookshop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE bookshop; -- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, phone VARCHAR(20), address VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 图书分类表 CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ); -- 图书表 CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, author VARCHAR(50), price DECIMAL(10,2) NOT NULL DEFAULT 0.00, stock INT NOT NULL DEFAULT 0, category_id INT, cover VARCHAR(200), description TEXT, FOREIGN KEY (category_id) REFERENCES t_category(id) ); -- 购物车表 CREATE TABLE t_cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, FOREIGN KEY (user_id) REFERENCES t_user(id), FOREIGN KEY (book_id) REFERENCES t_book(id) ); -- 订单主表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status INT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES t_user(id) ); -- 订单明细表 CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, book_id INT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL, FOREIGN KEY (order_id) REFERENCES t_order(id), FOREIGN KEY (book_id) REFERENCES t_book(id) );参数说明utf8mb4是为了支持中文书名和特殊符号status用整数表示订单状态0 待付款、1 已付款、2 已发货、3 已完成比存字符串省空间也好比较order_no加唯一索引防止重复下单生成相同订单号。初始数据至少插几条分类和图书否则前台页面是空的演示效果很差。提示MySQL 8 默认认证插件是caching_sha2_password老版本 JDBC 驱动连接会报错。要么升级驱动到 8.x要么建用户时指定mysql_native_password。3. 用 IDEA 搭起可运行的 JSP 项目骨架3.1 新建项目与目录结构在 IDEA 里新建 JavaWeb 项目很多人卡在“新建完没有 webapp 目录”。正确做法是先建一个普通 Java 项目再在项目结构里添加 Web 模块。具体步骤是 File → Project Structure → Modules → 加号 → Web然后把 Web Resource Directory 指到src/main/webapp。这样 IDEA 才会识别 JSP 和WEB-INF/web.xml。目录结构最终长这样src/main/java放 Java 代码src/main/webapp放 JSP、CSS、JSwebapp/WEB-INF/lib放 jar 包webapp/WEB-INF/web.xml放配置。依赖需要三个MySQL 驱动mysql-connector-j-8.x.jar、JSTL 的jstl.jar和standard.jar。把这三个丢进WEB-INF/libIDEA 里再 Add as Library否则 JSP 里用c:forEach会报找不到标签。3.2 数据库连接工具类与连接池课设里最容易翻车的地方就是数据库连接。每个 Servlet 都DriverManager.getConnection会导致连接数暴涨演示时点几下就卡死。常见做法是写一个工具类用静态代码块加载驱动并提供获取和关闭连接的方法。进阶一点用 Druid 或 C3P0 连接池但课设用简单工具类也够。package com.bookshop.util; import java.sql.*; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/bookshop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }逻辑说明静态代码块只执行一次保证驱动只加载一次。URL 里的serverTimezoneAsia/Shanghai不加会报时区错误useSSLfalse避免本地连接时的 SSL 警告。close方法按 ResultSet、Statement、Connection 的顺序关闭每个都判空避免空指针。参数上端口 3306 和库名 bookshop 按你本机改密码换成你自己的。注意如果启动报Error 2002 (HY000): Cant connect to local MySQL server先确认 MySQL 服务是否启动再确认端口没被占用。Windows 下用net start mysql80Linux 下用systemctl status mysqld看状态。3.3 实体类与 DAO 的写法实体类就是和表字段一一对应的 JavaBean属性名用驼峰比如categoryId对应category_id。DAO 里每个方法只做一件事查图书列表、按 ID 查图书、插入订单。下面以图书查询为例。package com.bookshop.dao; import com.bookshop.entity.Book; import com.bookshop.util.DBUtil; import java.sql.*; import java.util.*; public class BookDao { public ListBook findAll() { ListBook list new ArrayList(); String sql SELECT id, name, author, price, stock, category_id, cover FROM t_book ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setName(rs.getString(name)); b.setAuthor(rs.getString(author)); b.setPrice(rs.getBigDecimal(price)); b.setStock(rs.getInt(stock)); b.setCategoryId(rs.getInt(category_id)); b.setCover(rs.getString(cover)); list.add(b); } } catch (SQLException e) { e.printStackTrace(); } return list; } }逻辑说明用 try-with-resources 自动关闭连接比手动 close 更安全。SQL 用PreparedStatement而不是Statement防止 SQL 注入这也是答辩常问的点。getBigDecimal对应DECIMAL类型避免精度丢失。参数上ORDER BY id DESC让新书排在前面演示时更直观。4. 前台购物流程与后台管理的 Servlet 实现4.1 图书列表与详情页的请求链路前台的核心链路是浏览器请求/book/listServlet 调 DAO 拿数据存进 request 域转发到bookList.jspJSP 用 JSTL 遍历渲染。这个链路走通后面购物车和订单就是复制这套模式。package com.bookshop.servlet; import com.bookshop.dao.BookDao; import com.bookshop.entity.Book; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.util.List; WebServlet(/book/list) public class BookListServlet extends HttpServlet { private final BookDao bookDao new BookDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListBook books bookDao.findAll(); req.setAttribute(books, books); req.getRequestDispatcher(/WEB-INF/views/bookList.jsp).forward(req, resp); } }逻辑说明WebServlet注解代替 web.xml 配置路径/book/list就是访问地址。forward是服务器内部跳转浏览器地址栏不变request 域的数据能带到 JSP。如果用redirectrequest 域数据会丢这是新手最常见的翻车点。参数上doGet处理列表查询doPost留给表单提交不要混用。对应的 JSP 片段用 JSTL 渲染% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:forEach items${books} varb div classbook-item img src${pageContext.request.contextPath}/images/${b.cover} alt${b.name} h3${b.name}/h3 p作者${b.author}/p p价格${b.price}/p a href${pageContext.request.contextPath}/book/detail?id${b.id}查看详情/a /div /c:forEach${pageContext.request.contextPath}用来拼上下文路径项目部署名变了也不用改代码。c:forEach的items对应 Servlet 里 setAttribute 的 key写错一个字母页面就是空白排查时先看 key 是否一致。4.2 购物车与下单的事务处理购物车加购的逻辑是先查该用户购物车里是否已有这本书有就数量加一没有就插一条。下单时要把购物车里的条目转成订单明细同时扣减库存这三步必须在一个事务里否则会出现“订单生成了但库存没扣”的脏数据。public String createOrder(int userId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 String orderNo NO System.currentTimeMillis(); BigDecimal total BigDecimal.ZERO; // 1. 查购物车 String cartSql SELECT c.book_id, c.quantity, b.price FROM t_cart c JOIN t_book b ON c.book_id b.id WHERE c.user_id ?; ListObject[] items new ArrayList(); try (PreparedStatement ps conn.prepareStatement(cartSql)) { ps.setInt(1, userId); ResultSet rs ps.executeQuery(); while (rs.next()) { items.add(new Object[]{rs.getInt(book_id), rs.getInt(quantity), rs.getBigDecimal(price)}); total total.add(rs.getBigDecimal(price).multiply(new BigDecimal(rs.getInt(quantity)))); } } // 2. 插订单主表 String orderSql INSERT INTO t_order(order_no, user_id, total_price, status) VALUES(?,?,?,0); int orderId; try (PreparedStatement ps conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, orderNo); ps.setInt(2, userId); ps.setBigDecimal(3, total); ps.executeUpdate(); ResultSet keys ps.getGeneratedKeys(); keys.next(); orderId keys.getInt(1); } // 3. 插明细并扣库存 String itemSql INSERT INTO t_order_item(order_id, book_id, quantity, price) VALUES(?,?,?,?); String stockSql UPDATE t_book SET stock stock - ? WHERE id ? AND stock ?; for (Object[] it : items) { try (PreparedStatement ps conn.prepareStatement(itemSql)) { ps.setInt(1, orderId); ps.setInt(2, (Integer) it[0]); ps.setInt(3, (Integer) it[1]); ps.setBigDecimal(4, (BigDecimal) it[2]); ps.executeUpdate(); } try (PreparedStatement ps conn.prepareStatement(stockSql)) { ps.setInt(1, (Integer) it[1]); ps.setInt(2, (Integer) it[0]); ps.setInt(3, (Integer) it[1]); if (ps.executeUpdate() 0) { throw new SQLException(库存不足图书ID it[0]); } } } conn.commit(); return orderNo; } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); return null; } finally { if (conn ! null) try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) {} } }逻辑说明setAutoCommit(false)开启事务任何一步抛异常都 rollback保证订单、明细、库存三者一致。Statement.RETURN_GENERATED_KEYS拿自增主键用来关联明细。扣库存的 SQL 带AND stock ?更新行数为 0 说明库存不够主动抛异常触发回滚。参数上订单号用时间戳生成课设够用生产环境要用更严谨的分布式 ID。4.3 后台图书管理的增删改查后台管理是课设的加分项也是答辩老师最爱问的部分。图书管理至少要有列表、新增、编辑、删除四个功能。新增和编辑共用一个表单页靠 id 是否为空区分。删除用软删除还是物理删除课设里物理删除就行但要注意外键约束如果这本书已经被订单引用直接删会报外键错误。常见做法是先查订单明细里有没有这本书有就提示“该图书已被订单引用不能删除”。WebServlet(/admin/book/delete) public class BookDeleteServlet extends HttpServlet { private final BookDao bookDao new BookDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int id Integer.parseInt(req.getParameter(id)); if (bookDao.hasOrderItem(id)) { req.setAttribute(msg, 该图书已被订单引用不能删除); req.getRequestDispatcher(/admin/book/list).forward(req, resp); return; } bookDao.deleteById(id); resp.sendRedirect(req.getContextPath() /admin/book/list); } }逻辑说明先判断是否被订单引用是则带提示信息转发回列表否则删除后重定向。重定向用sendRedirect避免刷新页面重复删除。参数上id从 URL 取要判空和数字格式否则传个字母进来就抛NumberFormatException。5. 部署打包与常见问题排查5.1 传统 JSP 项目打包 war 与 Tomcat 部署课设最终要交的是能跑的项目打包成 war 是最稳的方式。IDEA 里 File → Project Structure → Artifacts → 加号 → Web Application: Archive选到你的 webapp 目录输出路径设成out/artifacts/bookshop_war。然后 Build → Build Artifacts生成的 war 丢进 Tomcat 的webapps目录启动 Tomcat 会自动解压。访问地址是http://localhost:8080/bookshop/其中 bookshop 是 war 包名。如果访问 404先看 Tomcat 日志里 war 有没有解压成功再看web.xml里 welcome-file 是否指向了正确的首页。常见做法是把首页设成index.jsp里面用jsp:forward跳到图书列表。提示Tomcat 10 以上版本把javax.servlet改成了jakarta.servlet老项目直接部署会报 ClassNotFound。课设如果用的是 Tomcat 9别升级到 10如果必须用 10所有 import 都要改。5.2 避坑JSP 课设里最容易翻车的五个点现象一页面中文全是乱码。原因是请求和响应编码没统一。解决在 Servlet 里resp.setContentType(text/html;charsetutf-8)和req.setCharacterEncoding(utf-8)JSP 页面顶部% page contentTypetext/html;charsetutf-8 %数据库连接 URL 加characterEncodingutf8三处缺一不可。现象二JSTL 标签不解析页面直接显示c:forEach。原因是缺 jstl.jar 或 web.xml 版本太低。解决确认WEB-INF/lib下有 jstl 和 standard 两个 jarweb.xml 的 schema 用 3.0 以上版本。现象三连接池耗尽点几下就卡死。原因是 DAO 里 Connection 没关。解决统一用 try-with-resources或者用 DBUtil.close 在 finally 里关。检查方法是在 MySQL 里执行SHOW PROCESSLIST看连接数是不是一直涨。现象四下单后库存没扣或者扣成负数。原因是没加事务或扣库存 SQL 没加条件。解决按 4.2 的写法setAutoCommit(false)加AND stock ?更新行数为 0 就回滚。现象五部署后图片 404。原因是图片路径写成了绝对路径或没放进 webapp。解决图片统一放webapp/imagesJSP 里用${pageContext.request.contextPath}/images/xxx.jpg不要用C:\...这种本地路径。6. 把课设做成能讲清楚的作品验证与进阶技巧课设做完不是终点能讲清楚才是。答辩时老师最爱问三个问题你的 MVC 怎么分的、事务怎么保证的、如果并发下单会怎样。前两个前面已经覆盖第三个是拉开差距的地方。当前实现在高并发下两个请求同时读到库存为 1都通过stock 1判断然后都扣减库存变 -1。解决办法是在扣库存 SQL 上加行锁或者用UPDATE t_book SET stock stock - ? WHERE id ? AND stock ?配合事务隔离级别MySQL 的 InnoDB 在更新时会加行锁第二个请求会等第一个提交后再判断从而避免超卖。这个点讲出来比单纯说“我用了事务”有说服力。验证方法上我习惯写一个简单的压测脚本用ab或 JMeter 对下单接口发 50 个并发然后查数据库库存和订单数是否对得上。命令是ab -n 100 -c 50 -p order.txt -T application/x-www-form-urlencoded http://localhost:8080/bookshop/order/create其中 order.txt 放用户 ID 参数。跑完看SELECT stock FROM t_book WHERE id1和SELECT COUNT(*) FROM t_order如果库存没变负、订单数等于成功请求数说明事务和锁生效了。进阶技巧上有两个方向值得花时间。一是把 JSP 里的 Java 代码彻底清掉全部用 JSTL 和 EL 表达式页面只负责展示这样代码可读性上一个台阶。二是给 DAO 加一个简单的连接池不用引入 Druid 那么重自己写一个LinkedListConnection做池化理解连接复用的原理。这两个改动不大但能让你的课设从“能跑”变成“有设计”。我自己做第一个 JSP 课设时把数据库密码硬编码在 DBUtil 里交上去被老师一眼看穿后来改成读db.properties配置文件虽然只是几行代码但答辩时老师明显更认可。还有一次订单号用new Date().getTime()生成压测时同一毫秒生成了两个相同订单号直接报唯一索引冲突后来加了随机数才解决。这些坑踩过一次就忘不掉希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →