JSP+Servlet+MySQL 在线购书系统 MVC 期末大作业实战指南
简介这是一套面向高校计算机相关专业学生的JavaWeb期末大作业与课程设计完整源码主题为在线购书系统采用JSPServletMySQL并遵循MVC三层架构适合需要提交高质量大作业或想通过实战理解Web开发流程的初学者与进阶者。压缩包共75个文件约47.77MB包含14个JSP页面、12个Java源文件及对应class、1个SQL数据库脚本另有CSS样式、JS脚本、XML配置、图片素材与项目展示视频覆盖前端展示、后端控制与数据持久化各层。目前已有349人学习下载说明该方案在同类作业中认可度较高。代码注释详尽新手也能读懂下载后简单部署即可运行可直接作为课程设计或期末大作业提交。项目功能完善、界面美观、操作便捷配套数据库文件与说明文档便于快速搭建环境、理解MVC分层思想并完成二次开发具有较高的参考与实用价值。1. 在线购书系统用 JSPServletMySQL 跑通 MVC这套期末大作业到底值不值得做如果你正在搜 javaweb 期末大作业大概率不是想听“JSP 已经过时了”这种话而是需要一个能编译、能连库、能演示、能讲清楚分层的完整项目。在线购书系统就是最经典的选题用户注册登录、图书列表、购物车、下单、后台管理功能边界清晰业务逻辑不复杂但足以把 JSPServletMySQL 的 MVC 三层架构完整跑一遍。它适合两类人一是期末需要交作业、答辩要讲清代码结构的学生二是刚学完 Servlet 想找一个能动手改、能加功能、能写进简历的练手项目。真正要解决的问题不是“能不能跑”而是“怎么把 Model、View、Controller 拆干净让答辩老师一眼看出你不是复制粘贴”。2. MVC 三层架构在购书系统里怎么落地从一张表到一次请求的完整链路2.1 为什么购书系统天然适合 MVC 分层在线购书系统的业务动作非常标准浏览器发起请求Servlet 接收参数并调用业务方法业务方法通过 JDBC 操作 MySQL结果再交给 JSP 渲染。这个流程和 MVC 的对应关系几乎是教科书级别的——Model 对应实体类和 DAOView 对应 JSPController 对应 Servlet。很多同学做期末作业时容易把 JDBC 代码直接写进 JSP页面里既有 HTML 又有 SQL改一个字段要翻三个文件。MVC 的价值就在这里把数据库操作收进 DAO把页面跳转收进 ServletJSP 只负责展示。后面加“图书分类筛选”或者“订单状态修改”时你只需要动对应的那一层不会牵一发动全身。从选型角度看JSPServlet 虽然老但它的请求响应模型最透明。Spring MVC 帮你封装了 DispatcherServlet很多人写完也不知道请求怎么到 Controller 的。而这个项目里每个 Servlet 都对应一个WebServlet注解或 web.xml 配置你答辩时能指着代码说清楚“这个请求先到谁、再到谁、最后到哪个 JSP”。对于期末大作业来说这种可解释性比技术新旧更重要。2.2 数据库设计五张核心表撑起购书主流程购书系统的表不用多但字段要能覆盖演示流程。下面这套表结构是我带过几届学生后收敛出来的最小可用集合MySQL 5.7 和 8.0 都能直接跑。-- 用户表登录、注册、后台用户管理 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100), phone VARCHAR(20), role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 图书表列表展示、详情、后台增删改 CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(200) NOT NULL, author VARCHAR(100), price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, category_id INT, cover_img VARCHAR(255), description TEXT, status TINYINT DEFAULT 1 COMMENT 1上架 0下架 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 分类表图书筛选 CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 购物车表加购、改数量、删除 CREATE TABLE cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, quantity INT DEFAULT 1, UNIQUE KEY uk_user_book (user_id, book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表下单、订单列表、后台查看 CREATE TABLE orders ( 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 TINYINT DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个参数值得注意。price用DECIMAL(10,2)而不是FLOAT因为金额计算用浮点会出现0.10.2!0.3的经典问题答辩时老师如果问“为什么不用 double”这就是得分点。cart表加了uk_user_book唯一索引防止同一用户重复插入同一本书业务层用INSERT ... ON DUPLICATE KEY UPDATE quantityquantity1就能实现“已存在则数量加一”。orders表的order_no用唯一索引生成规则可以用时间戳加用户 ID避免并发下单时订单号重复。2.3 一个请求的完整链路以“加入购物车”为例很多同学分层分不清是因为没有跟踪过一次完整请求。以“加入购物车”为例链路是这样的图书详情页book_detail.jsp里有一个表单提交到/cart/add。CartAddServlet接收bookId和quantity从 session 里取当前用户。Servlet 调用CartDao.addOrUpdate(userId, bookId, quantity)。DAO 里执行 SQL返回影响行数。Servlet 根据结果决定重定向到购物车列表还是返回错误提示。对应的 Servlet 核心代码WebServlet(/cart/add) public class CartAddServlet extends HttpServlet { private CartDao cartDao new CartDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 取当前登录用户未登录直接跳登录页 User user (User) req.getSession().getAttribute(loginUser); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 2. 参数解析quantity 缺省为 1 int bookId Integer.parseInt(req.getParameter(bookId)); int quantity req.getParameter(quantity) null ? 1 : Integer.parseInt(req.getParameter(quantity)); // 3. 调用 DAO已存在则累加数量 boolean ok cartDao.addOrUpdate(user.getId(), bookId, quantity); // 4. 根据结果跳转 if (ok) { resp.sendRedirect(req.getContextPath() /cart/list); } else { req.setAttribute(msg, 加入购物车失败请重试); req.getRequestDispatcher(/error.jsp).forward(req, resp); } } }这段代码里req.getSession().getAttribute(loginUser)是登录拦截的关键后面讲过滤器时会再提到。sendRedirect和forward的区别也是答辩常问重定向会改变浏览器地址栏适合操作成功后跳转转发地址栏不变适合携带错误信息回显。DAO 层的addOrUpdate用一条 SQL 搞定插入或更新public boolean addOrUpdate(int userId, int bookId, int quantity) { String sql INSERT INTO cart(user_id, book_id, quantity) VALUES(?,?,?) ON DUPLICATE KEY UPDATE quantity quantity ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, userId); ps.setInt(2, bookId); ps.setInt(3, quantity); ps.setInt(4, quantity); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }DBUtil是一个简单的 JDBC 工具类从db.properties读驱动、URL、用户名和密码。这里用try-with-resources自动关闭连接避免忘记close导致连接池耗尽。MySQL 8.0 的驱动类是com.mysql.cj.jdbc.Driver5.7 是com.mysql.jdbc.DriverURL 里要加useSSLfalseserverTimezoneAsia/Shanghai否则会报 SSL 警告或时区错误。3. 在 IDEA 里把项目跑起来Tomcat 配置、JSP 页面与图片坐标定位3.1 IDEA 运行 JavaWeb 项目的完整配置步骤搜 idea 运行 javaweb 项目配置 的人十有八九卡在“项目能编译但 404”或者“Tomcat 启动了但访问不到页面”。下面这套步骤按顺序走基本能避开大部分坑。第一步确认项目结构是 Dynamic Web Project 或 Maven Web 项目。如果是普通 Java 项目需要在Project Structure的Facets里添加Web把web目录设为 Web 资源目录web/WEB-INF/web.xml设为部署描述符。第二步配置 Artifacts。在Project Structure的Artifacts里新增Web Application: Exploded输出目录默认是out/artifacts/项目名_war_exploded。把Available Elements里的库全部双击加到WEB-INF/lib下否则运行时会报ClassNotFoundException。第三步配置 Tomcat Run Configuration。Server选项卡里Application server选本地 Tomcat 目录URL填http://localhost:8080/项目名/Deployment选项卡里添加刚才的 ArtifactApplication context填/项目名。这里注意如果Application context留空访问路径就是http://localhost:8080/但项目里的req.getContextPath()会返回空字符串重定向路径可能出错。建议统一填项目名。第四步检查web.xml版本。Servlet 3.0 以上可以用WebServlet注解如果web.xml的version是 2.5注解不生效所有 Servlet 必须在web.xml里手动配置。常见做法是直接用 4.0 的web.xml头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 /web-app第五步数据库连接测试。在db.properties里写好配置后写一个main方法调DBUtil.getConnection()能打印出连接对象就说明 JDBC 通了。如果报Access denied for user检查 MySQL 用户权限如果报Unknown database检查数据库是否已创建如果报The server time zone value在 URL 后加serverTimezoneAsia/Shanghai。3.2 JSP 个人信息展示页面与图片坐标定位搜 jsp 个人信息展示页面 和 jsp 图片如何对坐标定位 的人通常是在做用户中心或图书封面上的热点区域。个人信息展示页面的核心是从 session 取用户对象然后填充表单% page contentTypetext/html;charsetUTF-8 languagejava % html headtitle个人信息/title/head body % User user (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } % h2个人信息/h2 form action%request.getContextPath()%/user/update methodpost 用户名input typetext nameusername value%user.getUsername()% readonlybr 邮箱input typetext nameemail value%user.getEmail()%br 手机input typetext namephone value%user.getPhone()%br input typesubmit value保存修改 /form /body /html这里用户名设为readonly因为用户名是登录凭证一般不允许修改。邮箱和手机可以改提交到/user/update对应的 Servlet 处理。注意 JSP 里取 session 属性时要做空判断否则用户直接访问这个页面会报空指针。图片坐标定位通常用在图书封面上的“点击某区域跳转”或者“图片热区”。JSP 里可以用map和area实现img src%request.getContextPath()%/images/book_cover.jpg usemap#coverMap map namecoverMap area shaperect coords0,0,100,50 hrefdetail.jsp?id1 alt图书详情 area shaperect coords100,0,200,50 hrefcart/add?bookId1 alt加入购物车 /mapcoords的四个数字分别是左上角 x、左上角 y、右下角 x、右下角 y。如果图片是响应式缩放过的坐标会偏移常见做法是给图片设固定宽高或者用 JavaScript 根据实际渲染尺寸换算坐标。这个功能在购书系统里不是必须的但如果答辩想展示“前端交互细节”可以加在首页轮播图上。3.3 登录过滤器与字符编码过滤器MVC 项目里过滤器是横切关注点的最佳落点。登录过滤器和编码过滤器几乎是标配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); } }登录过滤器只拦截需要登录的路径比如/cart/*、/order/*、/user/*WebFilter(urlPatterns {/cart/*, /order/*, /user/*}) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }注意WebFilter的urlPatterns不要写成/*否则登录页和静态资源也会被拦截导致死循环。如果项目用web.xml配置过滤器url-pattern同理。4. 避坑与排查从 404 到中文乱码的 5 个血泪现场4.1 现象Tomcat 启动正常访问页面 404原因通常有三种Artifact 没部署、Application context配错、web.xml里welcome-file没配。先看 Tomcat 日志里有没有Deploying web application字样如果没有说明 Artifact 没加到 Deployment 里。如果有检查访问 URL 是否带了正确的 context path。最后确认web.xml里有没有welcome-file-list没有的话访问目录不会自动跳转到index.jsp。4.2 现象JSP 页面中文乱码数据库存进去也是问号原因分两层页面编码和数据库编码。JSP 顶部必须写% page contentTypetext/html;charsetUTF-8 languagejava %HTML 的meta charsetUTF-8也要加。数据库连接 URL 加useUnicodetruecharacterEncodingutf8。建表时用DEFAULT CHARSETutf8mb4。如果还乱码检查 MySQL 的character_set_server是不是latin1在my.ini或my.cnf里改成utf8mb4后重启服务。4.3 现象Servlet 里req.getParameter()取到 null原因通常是表单字段名和getParameter的参数不一致或者表单method是get但 Servlet 只重写了doPost。常见做法是 Servlet 同时重写doGet和doPostdoGet里直接调doPost。另外如果表单里有input typefilegetParameter取不到普通字段需要用Part或第三方上传组件。4.4 现象MySQL 8.0 连接报Public Key Retrieval is not allowed这是 MySQL 8.0 默认认证插件caching_sha2_password导致的。解决方式是在 JDBC URL 后加allowPublicKeyRetrievaltrueuseSSLfalse。如果还不行把用户认证插件改成mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;。注意生产环境不要随便关 SSL但期末作业本地跑通优先。4.5 现象购物车数量修改后总价没变原因通常是 JSP 里总价用 JavaScript 算的但修改数量后没有重新计算或者后端返回的购物车列表没有更新 session。常见做法是购物车列表页每次请求都从数据库重新查不要缓存在 session 里。如果一定要缓存修改数量后要同步更新 session 里的对象。另外quantity的输入框要加onchange事件提交表单或者用 AJAX 异步更新后刷新总价区域。5. 让答辩加分的一个技巧用订单状态机把业务讲深期末大作业最怕的是“功能堆了一堆但每个都浅”。如果你想让老师觉得这个项目有设计感可以在订单模块加一个状态机。订单状态不是随便改的而是按待付款 → 已付款 → 已发货 → 已完成流转每次流转校验当前状态是否允许。public enum OrderStatus { PENDING(0, 待付款), PAID(1, 已付款), SHIPPED(2, 已发货), FINISHED(3, 已完成); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } public static boolean canTransfer(int from, int to) { if (from PENDING.code to PAID.code) return true; if (from PAID.code to SHIPPED.code) return true; if (from SHIPPED.code to FINISHED.code) return true; return false; } }在OrderService里更新状态前先调canTransfer不合法就抛异常。这样答辩时你可以说“我的订单状态不是随便改的有状态机约束防止已发货订单被改回待付款。”这个点比多写十个 CRUD 页面更能体现你对业务的理解。验证方法也很简单在后台管理页面加一个“发货”按钮只对已付款订单显示在用户订单列表加一个“确认收货”按钮只对已发货订单显示。按钮的显示逻辑用 JSTL 的c:if判断后端再校验一次。这样前后端双重校验老师问起来也有话可说。最后说个我自己的习惯每次改完 DAO 层的 SQL我都会在 MySQL 客户端里先手动执行一遍确认字段名和条件都对再写进 Java 代码。这个习惯帮我省了至少几十次“重启 Tomcat 看报错”的时间。期末大作业拼的不是技术多新而是谁少翻车、谁讲得清。希望帮到你。本文还有配套的精品资源点击获取