尧图精选

JSP+Servlet+MySQL在线购书系统:MVC架构与部署排错全解析

🕒 发布时间:2026/9/12 17:22:57 📁 来源:尧图网络
简介面向JavaWeb课程设计与期末大作业的在线购书系统基于JSP、Servlet与MySQL技术栈采用经典MVC三层架构。代码注释十分详尽适合初学者快速理解前后端交互、业务逻辑与数据库操作。资源包共75个文件、约47.77MB包含12个核心类、14个动态页面、3个样式表、SQL数据库脚本、Eclipse工程配置、项目展示视频及README说明从页面展示、业务逻辑到数据存储均有完整对应功能完善且界面美观系统涵盖图书展示、购物车、订单管理、后台管理等核心功能。已有347人浏览学习可见其作为高分课程设计的参考价值。借助完整源码、可直接导入的数据库和操作演示读者能快速完成部署与二次开发省去从零设计表结构和页面流程的时间同时结合注释和文档深入理解MVC分层思想适合期末答辩、课程设计或自学巩固JavaWeb开发技能。1. 一套 JSPServletMySQL 的在线购书系统值得拆开看的不只是“满分”期末大作业里JSPServletMySQL 的组合至今仍是很多学校课程设计的默认选项。这套在线购书系统把用户注册登录、图书分类检索、购物车、下单、后台管理全部装进一个 MVC 三层架构代码注释写得很满数据库脚本直接给到新手拿过来也能照着看懂。对已经工作几年的人真正值得看的反而是 JSP 这种老技术在现代 Tomcat 和 MySQL 8 下的部署细节、会话管理、SQL 写法这些容易翻车的点。下面按我拆项目的习惯从分层结构、环境搭建、核心流程到排错和进阶加分项完整过一遍。2. MVC三层架构在线购书系统为什么这么分层2.1 JSPServlet在课程设计里依然常见分层是关键JSP 本质上是一种在 HTML 里写 Java 的视图技术Servlet 负责接收 HTTP 请求和处理逻辑数据落在 MySQL。很多大作业交上来就是几十个 JSP 互相跳转看起来能跑答辩时却讲不清楚哪些逻辑属于谁。这套在线购书系统没有走那条老路而是把项目切成了表现层、控制层、业务层、数据访问层代码注释也标注了每一层的作用。分层的首要价值是把改动隔离开。图书价格展示要加个格式化不需要改动 SQL数据库从 MySQL 5.7 换成 MySQL 8.0也不需要重写页面。第二个价值是答辩时好讲老师问“登录流程怎么实现的”你可以直接说“登录页面提交到 LoginServletServlet 调用 UserServiceUserService 再调 UserDao”一句话就能说清三层。2.2 三层职责和典型类在这个项目里最常见的包结构是 entity、dao、service、servlet、web。数据库脚本里有 book、user、order、order_item 这类表Java 侧就会有对应的实体类。按平时拆项目时的习惯整理成下表层次所在包/目录典型类职责实体模型entity 或 beanUser, Book, CartItem与数据表字段一一对应数据访问层daoUserDao, BookDao, OrderDao封装 JDBC 操作只负责 SQL业务层serviceUserService, OrderService处理登录校验、下单等业务规则控制层servlet 或 controllerLoginServlet, BookServlet接收请求、调用 service、决定跳转表现层WebContent / webapplogin.jsp, index.jsp展示数据和收集用户输入注意这张表里的 DAO 只做增删改查不写 if 判断业务service 层才处理“密码对不对”“库存够不够”。很多大作业被扣分就是因为把业务判断写进了 Servlet 或 JSP导致一个方法又长又难测。2.3 一次登录请求的MVC流转以登录功能为例这个项目里通常能看到这样的控制层代码。拿到源码后先找LoginServlet注释会告诉你请求是怎么一步步走完的WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 从请求中取出页面提交的两个参数 String username request.getParameter(username); String password request.getParameter(password); // 2. 调用业务层不在这里直接写 JDBC UserService userService new UserService(); User user userService.login(username, password); // 3. 业务层返回空说明用户名或密码有误 if (user ! null) { // 登录成功把用户信息放 Session再跳回首页 request.getSession().setAttribute(loginUser, user); response.sendRedirect(index.jsp); } else { // 登录失败带提示信息回登录页 request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } } }这段代码的关键在两个点一是WebServlet(/login)定义了访问路径前端表单actionlogin methodpost就会匹配到这里二是失败时用的forward地址栏还是 login.jsp本质上是在同一个请求里把页面内容替换掉。成功时用sendRedirect地址栏变成 index.jsp这样用户刷新页面不会重复提交登录表单。要注意的是如果这个项目运行在 Servlet 3.0 之前的容器WebServlet会不生效必须去 web.xml 里配置servlet和servlet-mapping。课程设计常用的 Tomcat 8.5 和 Tomcat 9 都支持注解但答辩机器上如果装的是 Tomcat 7就需要手动补 web.xml 配置。3. 环境搭建与数据库初始化先把项目和 MySQL 跑起来3.1 从压缩包到 Eclipse 可运行项目压缩包解开后根目录会看到.classpath、.settings、src、WebContent或main、mobiledatabase.sql、README.md。.classpath和.settings是 Eclipse 动态 Web 项目Dynamic Web Project的标记文件说明它不是普通 Java 工程导入时不要选Import Existing Maven Projects直接选Import Existing Projects into Workspace再选到解压后的根目录。导入后确认三个东西项目名上有没有小地球图标Libraries里有没有 Tomcat 的运行时依赖。如果没有运行环境项目会全部飘红。右键项目Build Path Configure Build Path Add Library Server Runtime选本机 Tomcat。注意这里选的是 Tomcat 运行环境不是把 jar 单独塞进 classpath否则 Servlet 和 HttpServlet 这些类永远缺依赖。3.2 数据库导入与 JDBC 配置数据库脚本在压缩包根目录文件名是mobiledatabase.sql。最简单的方式是用命令行导入避免 IDE 图形界面容易遇到的编码问题。mysql -uroot -p --default-character-setutf8 mobiledatabase.sql如果 MySQL 8 的命令行工具不识别mobiledatabase.sql中的中文字符可以在 Windows 下改用cmd /c mysql -uroot -p --default-character-setutf8 -e \source D:/javaweb_book/mobiledatabase.sql\说明--default-character-setutf8强制客户端用 UTF-8 读取 SQL 文件避免建表后中文变成??-e source ...是 mysql 命令行里执行 SQL 脚本的另一种方式适合脚本路径带空格或括号的环境。导入完成后用show tables;查看应该能看到book、user、order_item之类的表。JDBC 连接信息一般在src下的db.properties或jdbc.properties常见写法是jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/mobiledatabase?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456这里最容易踩坑的是驱动类名。MySQL 5.7 及之前习惯写com.mysql.jdbc.DriverMySQL 8.0 之后必须换成com.mysql.cj.jdbc.Driver否则连接时提示ClassNotFoundException或Failed to load driver class。按版本整理成下表MySQL 版本驱动类连接 URL 关键参数5.7 及之前com.mysql.jdbc.DriveruseUnicodetruecharacterEncodingutf88.0 及以上com.mysql.cj.jdbc.DriverserverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai是 MySQL 8 连接时的常见必填参数不写可能在建立连接时抛异常allowPublicKeyRetrievaltrue是为了兼容 MySQL 8 的 caching_sha2_password 认证插件遇到Public Key Retrieval is not allowed时加上这个参数即可。3.3 启动 Tomcat 前要确认的三个参数启动前把三件事在Server视图里过一遍。第一Tomcat 版本要和动态 Web 模块Dynamic Web Module匹配这个项目的org.eclipse.wst.common.project.facet.core.xml里写的版本如果和 Tomcat 不一致运行时会报Invalid web project。第二db.properties里的jdbc.password必须改成自己本机 MySQL 的密码不要直接拿原项目里的密码启动。第三在项目属性里找到Web Project Settings记下 Context Root默认可能是/BookStore或/online-book-store访问首页时用的是这个路径不是项目文件系统里的目录名。4. 核心功能实现图书检索、购物车与订单的代码走读4.1 图书列表DAO Servlet JSP 的最小闭环在线购书系统最常被点到的主流程是“用户打开首页 → 看到图书列表 → 点分类 → 进入图书详情 → 加购物车”。图书列表是这个流程的起点也是 DAO 层最典型的写法。public ListBook findByCategory(int categoryId) { String sql SELECT * FROM book WHERE category_id ?; try (Connection conn DBUtils.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, categoryId); try (ResultSet rs ps.executeQuery()) { ListBook list new ArrayList(); while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setTitle(rs.getString(title)); b.setPrice(rs.getBigDecimal(price)); b.setStock(rs.getInt(stock)); list.add(b); } return list; } } catch (SQLException e) { throw new RuntimeException(e); } }这里用PreparedStatement而不是Statement是因为?占位符会把参数作为纯字符串传给数据库而不是拼接进 SQL能够挡住最基本的单引号注入。DBUtils.getConnection()是项目里公共的数据库连接工具通常会从 db.properties 里读取 URL、用户名和密码每次调用都新建连接课程设计这样写够用。Servlet 拿到list后会把它放到request作用域再 forward 到book_list.jsprequest.setAttribute(bookList, list); request.getRequestDispatcher(book_list.jsp).forward(request, response);JSP 页面里用 EL 表达式取数据比在% %里写大段 Java 清晰得多。如果页面里出现% for(int i0;i...这种脚本片段建议改写成c:forEach后面第 6 章会说。4.2 购物车用 Session 还是 Cookie购物车是这个项目的核心加分点。常见做法是把CartItem列表放进 Session因为购物车是“用户私有的、临时性的”数据不需要落库用户关掉浏览器后就清空。Cookie 方案则适合“未登录也能加购物车下次打开还在”的场景但要自己处理过期时间和序列化课设阶段不推荐。方案存储位置优点缺点适用点Session服务器内存实现简单对象直接存取用户量大时占内存本系统默认推荐Cookie浏览器持久化到客户端大小4KB限制需JSON序列化记住登录状态代码一般长这样Cart cart (Cart) request.getSession().getAttribute(cart); if (cart null) { cart new Cart(); request.getSession().setAttribute(cart, cart); } cart.add(bookId, quantity);注意这里cart对象本身是可变的。第一次访问时放入 Session之后每次拿到的都是同一个对象不需要再次 put。常见错误是每次 add 都重新new Cart()导致加进去的商品下一行就消失。4.3 下单事务Connection 和 PreparedStatement下单要同时写订单主表和订单明细表必须保证要么都成功要么都失败。这个项目如果用了事务通常会在OrderService里写成这样public void createOrder(Order order, ListOrderItem items) { Connection conn DBUtils.getConnection(); try { conn.setAutoCommit(false); String sql1 INSERT INTO order (user_id, total_price, create_time) VALUES (?,?,?); try (PreparedStatement ps conn.prepareStatement(sql1, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, order.getUserId()); ps.setBigDecimal(2, order.getTotalPrice()); ps.setTimestamp(3, order.getCreateTime()); ps.executeUpdate(); try (ResultSet rs ps.getGeneratedKeys()) { rs.next(); order.setId(rs.getInt(1)); } } // 拿到新订单ID后循环插入明细 for (OrderItem item : items) { String sql2 INSERT INTO order_item (order_id, book_id, quantity, price) VALUES (?,?,?,?); try (PreparedStatement ps conn.prepareStatement(sql2)) { ps.setInt(1, order.getId()); ps.setInt(2, item.getBookId()); ps.setInt(3, item.getQuantity()); ps.setBigDecimal(4, item.getPrice()); ps.executeUpdate(); } } conn.commit(); } catch (SQLException e) { conn.rollback(); throw new RuntimeException(下单失败, e); } finally { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { // 记日志 } } }这段要说明两个点。Statement.RETURN_GENERATED_KEYS用来拿数据库自增主键这样明细表才知道order_id是多少。setAutoCommit(false)之后所有 SQL 都在同一个事务里前置 SQL 异常时 rollback 会把前面已插入的数据回滚掉。新手最容易漏的是finally里没有把连接状态还原连接回到连接池后仍然保持着关闭自动提交的状态后续请求就会踩到莫名其妙的“未提交”问题。5. 部署和排错把“满分项目”真正跑在自己机器上5.1 最常见的五个启动失败原因这个资源经常遇到的问题整理成自检清单照着顺序排查比盲目重新导入快很多。报错现象可能原因处理方式The superclass javax.servlet.http.HttpServlet was not found项目没配置 Tomcat 运行环境项目 Build Path 中添加 Server RuntimeClassNotFoundException: com.mysql.jdbc.DriverMySQL 8 用了旧驱动或 jar 不在 WEB-INF/lib换com.mysql.cj.jdbc.Driverjar 放进WEB-INF/libAccess denied for user rootlocalhostdb.properties 密码与本机不一致修改 properties 后重新部署java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowedURL 缺少allowPublicKeyRetrievaltrue在 jdbc.url 后追加该参数访问首页 404Context Root 和实际访问路径不一致在 Server 视图里查 Context Root 或直接右键项目 Run As Run on Server很多同学卡在第一步报错信息指向javax.servlet.http.HttpServlet。这不是代码问题是 Eclipse 没有把 Tomcat 的 servlet-api.jar 提供给编译环境。第 3.1 里的 Build Path 配置需要重新确认一遍。5.2 把 JSP 编译 class 文件的位置也检查一遍JSP 第一次被访问时Tomcat 会把.jsp翻译成.java再编译成.class默认放在 Tomcat 安装目录的work/Catalina/localhost/项目名/org/apache/jsp下。如果改了 JSP 但刷新页面后还是旧效果可以强制重新编译# 先停掉 Tomcat再删除 work 目录下对应项目的编译缓存 rm -rf $CATALINA_BASE/work/Catalina/localhost/你的项目名Windows 上对应操作是把work\Catalina\localhost\项目名文件夹删掉然后重启 Tomcat让所有 JSP 重新翻译编译。这个思路也适用于改过web.xml或 Servlet 后不生效的场景比反复点 Clean 更彻底。5.3 中文乱码和静态资源 404页面中文乱码通常是三个地方没统一 UTF-8JSP 文件头的contentType、数据库连接 URL 里的characterEncodingutf8、数据库表本身的字符集。前两个改起来容易最后这个要用 SQL 检查SHOW CREATE TABLE book;看到DEFAULT CHARACTER SETutf8或utf8mb4就没问题。如果建表时是 latin1字符集不匹配会出现“已导入数据是乱码页面怎么调都救不回来”最好的办法是重新执行 SQL 脚本。静态资源 404 的常见原因是过滤器的拦截路径写成/*把 css、js、images 也挡了。这时要先在浏览器 F12 看 Network 里是哪个请求 404再检查项目的web.xml里有没有针对静态资源的mime-mapping或把 servlet 的url-pattern改成/book/*这类前缀模式不要用/*。6. 给大作业加分的几个进阶点防SQL注入、过滤器、统一异常处理6.1 把 DAO 层全部换成 PreparedStatement第 4 章的例子已经用了 PreparedStatement但项目里可能还有字符串拼接查询String sql SELECT * FROM book WHERE title LIKE % keyword %;这种写法在答辩时容易被追问 SQL 注入。改成占位符String sql SELECT * FROM book WHERE title LIKE ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, % keyword %);逻辑不变但 keyword 成了参数不参与 SQL 编译。把项目里所有 createStatement 的地方替换掉是最快的加固方式。6.2 用 Filter 做登录拦截下单、个人中心、后台管理都应要求登录。如果每个 Servlet 都写 session 判断重复代码很多。用 Filter 统一拦截更干净WebFilter(urlPatterns {/cart/*, /order/*, /admin/*}) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; if (request.getSession().getAttribute(loginUser) null) { ((HttpServletResponse) resp).sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }urlPatterns 必须和实际 Servlet 映射一致。chain.doFilter 表示放行未登录直接回登录页。6.3 用 JSTL 替换 JSP 脚本片段JSP 里到处% %很难读。列表页改成 JSTLc:forEach items${bookList} varbook tr td${book.id}/td td${book.title}/td td${book.price}/td /tr /c:forEach记得加 taglib 指令并确认 web.xml 的 JSP 版本在 2.4 以上EL 才会默认开启。这三个改法都建立在本项目现有结构上不会破坏 MVC 分层。改完后重新部署重点验证登录拦截是否误伤静态资源以及购物车下单流程是否正常走完。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →